Version 1.6 - important fix for high memory usage

This commit is contained in:
OTCv8 2020-01-01 23:22:56 +01:00
parent d15cc347dc
commit 1d2bdf855d
21 changed files with 83 additions and 322 deletions

View File

@ -1,6 +1,6 @@
# OTClientV8 # OTClientV8
Tibia client design for versions 7.40 - 10.99 Tibia client design for versions 7.40 - 11.00
It's based on https://github.com/edubart/otclient and it's not backward compatible. It's based on https://github.com/edubart/otclient and it's not backward compatible.
## DISCORD: https://discord.gg/feySup6 ## DISCORD: https://discord.gg/feySup6
@ -42,7 +42,11 @@ It's based on https://github.com/edubart/otclient and it's not backward compatib
# Paid version # Paid version
The difference between paid version and this one is that the 1st one comes with c++ sources and has better support. You may need c++ source if you want to add some more advanced modifications, better encryption, bot protection or some other things. The free version doesn't offer technical support, you need to follow tutorials and in case of any bug or problem you should submit an issue on github. Visit http://otclient.ovh if you want to know more about paid version and other extra services. The difference between paid version and this one is that the 1st one comes with c++ sources and has better support. You may need c++ source if you want to add some more advanced modifications, better encryption, bot protection or some other things. The free version doesn't offer technical support, you need to follow tutorials and in case of any bug or problem you should submit an issue on github. Visit http://otclient.ovh if you want to know more about paid version and other extra services.
# Quick Start # Quick Start for players
Download whole repository and run binary.
# Quick Start for server owners
Open `init.lua` and edit: Open `init.lua` and edit:

View File

@ -1,96 +0,0 @@
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}`)
}
});

View File

@ -1,36 +0,0 @@
{
"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"
}

View File

@ -1,84 +0,0 @@
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) {
}

View File

@ -1,22 +0,0 @@
{
"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"
}
}

View File

@ -1,11 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"lib": ["es6"],
"sourceMap": true
},
"exclude": [
"node_modules"
]
}

View File

@ -1,5 +0,0 @@
{
"dependencies": {
"mysql2": "registry:npm/mysql2#1.1.1+20170314181009"
}
}

View File

@ -14,11 +14,11 @@ Services = {
-- Servers accept http login url, websocket login url or ip:port:version -- Servers accept http login url, websocket login url or ip:port:version
Servers = { Servers = {
OTClientV8 = "http://otclient.ovh/api/login.php", -- OTClientV8 = "http://otclient.ovh/api/login.php",
OTClientV8Websocket = "wss://otclient.ovh:3000/", -- OTClientV8Websocket = "wss://otclient.ovh:3000/",
OTClientV8proxy = "http://otclient.ovh/api/login.php?proxy=1", -- OTClientV8proxy = "http://otclient.ovh/api/login.php?proxy=1",
OTClientV8c = "otclient.ovh:7171:1099:25:30:80:90", -- OTClientV8ClassicWithFeatures = "otclient.ovh:7171:1099:25:30:80:90",
OTClientV8Test = "http://otclient.ovh/api/login2.php", -- OTClientV8Classic = "otclient.ovh:7171:1099"
} }
ALLOW_CUSTOM_SERVERS = true -- if true it shows option ANOTHER on server list ALLOW_CUSTOM_SERVERS = true -- if true it shows option ANOTHER on server list
-- CONFIG END -- CONFIG END

View File

@ -31,14 +31,6 @@ local function tryLogin(charInfo, tries)
CharacterList.hide() CharacterList.hide()
-- proxies for not http login users
if charInfo.worldHost == "0.0.0.0" and g_proxy then
g_proxy.clear()
-- g_proxy.addProxy(proxyHost, proxyPort, proxyPriority)
g_proxy.addProxy("163.172.147.135", 7162, 0)
g_proxy.addProxy("158.69.68.42", 7162, 0)
end
g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey) g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey)
g_logger.info("Login to " .. charInfo.worldHost .. ":" .. charInfo.worldPort) g_logger.info("Login to " .. charInfo.worldHost .. ":" .. charInfo.worldPort)
loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...')) loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...'))
@ -243,9 +235,11 @@ function CharacterList.terminate()
CharacterList = nil CharacterList = nil
end end
function CharacterList.create(characters, account, otui) function CharacterList.create(characters, account, otui, websocket)
if not otui then otui = 'characterlist' end if not otui then otui = 'characterlist' end
if websocket then
websocket:close()
end
if charactersWindow then if charactersWindow then
charactersWindow:destroy() charactersWindow:destroy()
end end

View File

@ -15,7 +15,7 @@ local serverSelector
local clientVersionSelector local clientVersionSelector
local serverHostTextEdit local serverHostTextEdit
local rememberPasswordBox local rememberPasswordBox
local protos = {"740", "760", "772", "800", "810", "854", "860", "1077", "1090", "1096", "1098", "1099", "1100"} local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "1077", "1090", "1096", "1098", "1099", "1100"}
local webSocket local webSocket
local webSocketLoginPacket local webSocketLoginPacket
@ -54,9 +54,13 @@ local function onCharacterList(protocol, characters, account, otui)
loadBox = nil loadBox = nil
end end
CharacterList.create(characters, account, otui) CharacterList.create(characters, account, otui, webSocket)
CharacterList.show() CharacterList.show()
if webSocket then
webSocket = nil
end
g_settings.save() g_settings.save()
end end
@ -76,18 +80,22 @@ end
local function validateThings(things) local function validateThings(things)
local incorrectThings = "" local incorrectThings = ""
local missingFiles = false
local versionForMissingFiles = 0
if things ~= nil then if things ~= nil then
local thingsNode = {} local thingsNode = {}
for thingtype, thingdata in pairs(things) do for thingtype, thingdata in pairs(things) do
thingsNode[thingtype] = thingdata[1] thingsNode[thingtype] = thingdata[1]
if not g_resources.fileExists("/data/things/" .. thingdata[1]) then if not g_resources.fileExists("/data/things/" .. thingdata[1]) then
correctThings = false
incorrectThings = incorrectThings .. "Missing file: " .. thingdata[1] .. "\n" incorrectThings = incorrectThings .. "Missing file: " .. thingdata[1] .. "\n"
end missingFiles = true
local localChecksum = g_resources.fileChecksum("/data/things/" .. thingdata[1]):lower() versionForMissingFiles = thingdata[1]:split("/")[1]
if localChecksum ~= thingdata[2]:lower() and #thingdata[2] > 1 then else
if g_resources.isLoadedFromArchive() then -- ignore checksum if it's test/debug version local localChecksum = g_resources.fileChecksum("/data/things/" .. thingdata[1]):lower()
incorrectThings = incorrectThings .. "Invalid checksum of file: " .. thingdata[1] .. " (is " .. localChecksum .. ", should be " .. thingdata[2]:lower() .. ")\n" if localChecksum ~= thingdata[2]:lower() and #thingdata[2] > 1 then
if g_resources.isLoadedFromArchive() then -- ignore checksum if it's test/debug version
incorrectThings = incorrectThings .. "Invalid checksum of file: " .. thingdata[1] .. " (is " .. localChecksum .. ", should be " .. thingdata[2]:lower() .. ")\n"
end
end end
end end
end end
@ -95,6 +103,12 @@ local function validateThings(things)
else else
g_settings.setNode("things", {}) g_settings.setNode("things", {})
end end
if missingFiles then
incorrectThings = incorrectThings .. "\nYou should open data/things and create directory " .. versionForMissingFiles ..
".\nIn this directory (data/things/" .. versionForMissingFiles .. ") you should put missing\nfiles (Tibia.dat and Tibia.spr) " ..
"from correct Tibia version."
end
return incorrectThings return incorrectThings
end end
@ -177,10 +191,6 @@ local function onHTTPResult(data, err)
end end
end end
if webSocket then
webSocket:close()
webSocket = nil
end
onCharacterList(nil, characters, account, nil) onCharacterList(nil, characters, account, nil)
end end
@ -310,22 +320,16 @@ function EnterGame.checkWebsocket()
if webSocket then if webSocket then
webSocket:close() webSocket:close()
webSocket = nil webSocket = nil
newLogin.code:setText("")
end end
return return
end end
if webSocket then if webSocket then
if webSocket.url == url then if webSocket.url == url then
if newLogin:isHidden() and newLogin.code:getText():len() > 1 then
newLogin:show()
newLogin:raise()
end
return return
end end
webSocket:close() webSocket:close()
webSocket = nil webSocket = nil
end end
newLogin.code:setText("")
webSocket = HTTP.WebSocketJSON(url, { webSocket = HTTP.WebSocketJSON(url, {
onOpen = function(message, webSocketId) onOpen = function(message, webSocketId)
if webSocket and webSocket.id == webSocketId then if webSocket and webSocket.id == webSocketId then
@ -338,8 +342,8 @@ function EnterGame.checkWebsocket()
webSocketLoginPacket = nil webSocketLoginPacket = nil
EnterGame.hide() EnterGame.hide()
onHTTPResult(message, nil) onHTTPResult(message, nil)
elseif message.type == "quick_login" and message.code and message.qrcode then elseif message.type == "quick_login" and message.qrcode then
EnterGame.showNewLogin(message.code, message.qrcode) EnterGame.showNewLogin(message.qrcode)
end end
end end
end, end,
@ -365,10 +369,15 @@ function EnterGame.hideNewLogin()
newLogin:hide() newLogin:hide()
end end
function EnterGame.showNewLogin(code, qrcode) function EnterGame.showNewLogin(qrcode)
if enterGame:isHidden() then return end if enterGame:isHidden() then return end
newLogin.code:setText(code) newLogin.qrcode:setQRCode("https://quath.co/0/" .. qrcode, 1)
newLogin.qrcode:setQRCode(qrcode, 1) newLogin.qrcode:setEnabled(true)
local clickFunction = function()
g_platform.openUrl("qauth://" .. qrcode)
end
newLogin.qrcode.onClick = clickFunction
newLogin.quathlogo.onClick = clickFunction
if newLogin:isHidden() then if newLogin:isHidden() then
newLogin:show() newLogin:show()
newLogin:raise() newLogin:raise()
@ -449,7 +458,7 @@ function EnterGame.doLogin()
local incorrectThings = validateThings(things) local incorrectThings = validateThings(things)
if #incorrectThings > 0 then if #incorrectThings > 0 then
g_logger.info(incorrectThings) g_logger.error(incorrectThings)
if Updater then if Updater then
return Updater.updateThings(things, incorrectThings) return Updater.updateThings(things, incorrectThings)
else else

View File

@ -3,11 +3,11 @@ StaticWindow
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
margin-right: 20 margin-right: 20
id: newLoginPanel id: newLoginPanel
width: 230 width: 240
height: 350 height: 320
!text: tr('Quick Login & Registration') !text: tr('Quick Login & Registration')
Label UIButton
id: qrcode id: qrcode
width: 200 width: 200
height: 200 height: 200
@ -17,7 +17,7 @@ StaticWindow
image-smooth: false image-smooth: false
margin-top: 5 margin-top: 5
Label UIButton
id: quathlogo id: quathlogo
width: 66 width: 66
height: 40 height: 40
@ -33,28 +33,18 @@ StaticWindow
anchors.right: qrcode.right anchors.right: qrcode.right
text-align: center text-align: center
text-auto-resize: true text-auto-resize: true
!text: tr("Scan QR code or process\nbellow code to register or login") !text: tr("Scan or click QR code\nto register or login")
height: 40 height: 40
margin-top: 10 margin-top: 10
margin-bottom: 5 margin-bottom: 5
Label Button
id: code
height: 20
anchors.top: prev.bottom anchors.top: prev.bottom
anchors.left: prev.left anchors.left: parent.left
anchors.right: prev.right anchors.right: parent.right
text-align: center text-align: center
font: sans-bold-16px !text: tr("Click to get PC/Android/iOS app")
margin-top: 10
text: XXXXXX
Label
anchors.top: prev.bottom
anchors.left: prev.left
anchors.right: prev.right
text-align: center
!text: tr("Click to get Android/iOS app")
height: 20 height: 20
margin-top: 10 margin-top: 10
color: #FFFFFF color: #FFFFFF
@onClick: g_platform.openUrl("http://qauth.co")

View File

@ -116,6 +116,7 @@ function sendStats()
classic = tostring(g_settings.getBoolean("classicView")), classic = tostring(g_settings.getBoolean("classicView")),
fullscreen = tostring(g_window.isFullscreen()), fullscreen = tostring(g_window.isFullscreen()),
vsync = tostring(g_settings.getBoolean("vsync")), vsync = tostring(g_settings.getBoolean("vsync")),
autoReconnect = tostring(g_settings.getBoolean("autoReconnect")),
window_width = g_window.getWidth(), window_width = g_window.getWidth(),
window_height = g_window.getHeight(), window_height = g_window.getHeight(),
player_name = g_game.getCharacterName(), player_name = g_game.getCharacterName(),
@ -132,7 +133,8 @@ function sendStats()
cpu = g_platform.getCPUName(), cpu = g_platform.getCPUName(),
mem = g_platform.getTotalSystemMemory(), mem = g_platform.getTotalSystemMemory(),
mem_usage = g_platform.getMemoryUsage(), mem_usage = g_platform.getMemoryUsage(),
os_name = g_platform.getOSName() os_name = g_platform.getOSName(),
uptime = g_clock.seconds()
} }
} }
if g_proxy then if g_proxy then

View File

@ -9,9 +9,9 @@ end
function updateFeatures(version) function updateFeatures(version)
g_game.resetFeatures() g_game.resetFeatures()
-- you can add custom features here, list of them in modules\gamelib\const.lua -- you can add custom features here, list of them is in the modules\gamelib\const.lua
g_game.enableFeature(GameBot) g_game.enableFeature(GameBot)
g_game.enableFeature(GameMinimapLimitedToSingleFloor) --g_game.enableFeature(GameMinimapLimitedToSingleFloor) -- it will generate minimap only for current floor
--g_game.enableFeature(GameSpritesAlphaChannel) --g_game.enableFeature(GameSpritesAlphaChannel)
if(version >= 770) then if(version >= 770) then
@ -91,6 +91,10 @@ function updateFeatures(version)
g_game.enableFeature(GameAdditionalVipInfo) g_game.enableFeature(GameAdditionalVipInfo)
end end
if(version >= 972) then
g_game.enableFeature(GameDoublePlayerGoodsMoney)
end
if(version >= 980) then if(version >= 980) then
g_game.enableFeature(GamePreviewState) g_game.enableFeature(GamePreviewState)
g_game.enableFeature(GameClientVersion) g_game.enableFeature(GameClientVersion)

View File

@ -67,7 +67,12 @@ end
function bindKeys() function bindKeys()
gameRootPanel:setAutoRepeatDelay(10) gameRootPanel:setAutoRepeatDelay(10)
g_keyboard.bindKeyPress('Escape', function() g_game.cancelAttackAndFollow() end, gameRootPanel) local lastAction = 0
g_keyboard.bindKeyPress('Escape', function()
if lastAction + 50 > g_clock.millis() then return end
lastAction = g_clock.millis()
g_game.cancelAttackAndFollow()
end, gameRootPanel)
g_keyboard.bindKeyPress('Ctrl+=', function() if g_game.getFeature(GameNoDebug) then return end gameMapPanel:zoomIn() end, gameRootPanel) g_keyboard.bindKeyPress('Ctrl+=', function() if g_game.getFeature(GameNoDebug) then return end gameMapPanel:zoomIn() end, gameRootPanel)
g_keyboard.bindKeyPress('Ctrl+-', function() if g_game.getFeature(GameNoDebug) then return end gameMapPanel:zoomOut() end, gameRootPanel) g_keyboard.bindKeyPress('Ctrl+-', function() if g_game.getFeature(GameNoDebug) then return end gameMapPanel:zoomOut() end, gameRootPanel)
g_keyboard.bindKeyDown('Ctrl+Q', function() tryLogout(false) end, gameRootPanel) g_keyboard.bindKeyDown('Ctrl+Q', function() tryLogout(false) end, gameRootPanel)

View File

@ -7,6 +7,7 @@ lastFinishedStep = 0
autoWalkEvent = nil autoWalkEvent = nil
firstStep = true firstStep = true
walkLock = 0 walkLock = 0
walkEvent = nil
lastWalk = 0 lastWalk = 0
lastTurn = 0 lastTurn = 0
lastTurnDirection = 0 lastTurnDirection = 0
@ -202,7 +203,7 @@ function changeWalkDir(dir, pop)
end end
function smartWalk(dir) function smartWalk(dir)
scheduleEvent(function() walkEvent = scheduleEvent(function()
if g_keyboard.getModifiers() == KeyboardNoModifier then if g_keyboard.getModifiers() == KeyboardNoModifier then
local direction = smartWalkDir or dir local direction = smartWalkDir or dir
walk(direction) walk(direction)
@ -371,6 +372,8 @@ function turn(dir, repeated)
return return
end end
removeEvent(walkEvent)
if not repeated or (lastTurn + 100 < g_clock.millis()) then if not repeated or (lastTurn + 100 < g_clock.millis()) then
g_game.turn(dir) g_game.turn(dir)
changeWalkDir(dir) changeWalkDir(dir)

View File

@ -162,6 +162,10 @@ GamePrey = 78
GameExtendedOpcode = 80 GameExtendedOpcode = 80
GameMinimapLimitedToSingleFloor = 81 GameMinimapLimitedToSingleFloor = 81
GameDoubleLevel = 83
GameDoubleSoul = 84
GameDoublePlayerGoodsMoney = 85
GameNewWalking = 90 GameNewWalking = 90
GameSlowerManualWalking = 91 GameSlowerManualWalking = 91
GameExtendedNewWalking = 92 GameExtendedNewWalking = 92

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.