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
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.
## 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
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:

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 = {
OTClientV8 = "http://otclient.ovh/api/login.php",
OTClientV8Websocket = "wss://otclient.ovh:3000/",
OTClientV8proxy = "http://otclient.ovh/api/login.php?proxy=1",
OTClientV8c = "otclient.ovh:7171:1099:25:30:80:90",
OTClientV8Test = "http://otclient.ovh/api/login2.php",
-- OTClientV8 = "http://otclient.ovh/api/login.php",
-- OTClientV8Websocket = "wss://otclient.ovh:3000/",
-- OTClientV8proxy = "http://otclient.ovh/api/login.php?proxy=1",
-- OTClientV8ClassicWithFeatures = "otclient.ovh:7171:1099:25:30:80:90",
-- OTClientV8Classic = "otclient.ovh:7171:1099"
}
ALLOW_CUSTOM_SERVERS = true -- if true it shows option ANOTHER on server list
-- CONFIG END

View File

@ -31,14 +31,6 @@ local function tryLogin(charInfo, tries)
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_logger.info("Login to " .. charInfo.worldHost .. ":" .. charInfo.worldPort)
loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...'))
@ -243,9 +235,11 @@ function CharacterList.terminate()
CharacterList = nil
end
function CharacterList.create(characters, account, otui)
function CharacterList.create(characters, account, otui, websocket)
if not otui then otui = 'characterlist' end
if websocket then
websocket:close()
end
if charactersWindow then
charactersWindow:destroy()
end

View File

@ -15,7 +15,7 @@ local serverSelector
local clientVersionSelector
local serverHostTextEdit
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 webSocketLoginPacket
@ -54,9 +54,13 @@ local function onCharacterList(protocol, characters, account, otui)
loadBox = nil
end
CharacterList.create(characters, account, otui)
CharacterList.create(characters, account, otui, webSocket)
CharacterList.show()
if webSocket then
webSocket = nil
end
g_settings.save()
end
@ -76,14 +80,17 @@ end
local function validateThings(things)
local incorrectThings = ""
local missingFiles = false
local versionForMissingFiles = 0
if things ~= nil then
local thingsNode = {}
for thingtype, thingdata in pairs(things) do
thingsNode[thingtype] = thingdata[1]
if not g_resources.fileExists("/data/things/" .. thingdata[1]) then
correctThings = false
incorrectThings = incorrectThings .. "Missing file: " .. thingdata[1] .. "\n"
end
missingFiles = true
versionForMissingFiles = thingdata[1]:split("/")[1]
else
local localChecksum = g_resources.fileChecksum("/data/things/" .. thingdata[1]):lower()
if localChecksum ~= thingdata[2]:lower() and #thingdata[2] > 1 then
if g_resources.isLoadedFromArchive() then -- ignore checksum if it's test/debug version
@ -91,10 +98,17 @@ local function validateThings(things)
end
end
end
end
g_settings.setNode("things", thingsNode)
else
g_settings.setNode("things", {})
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
end
@ -177,10 +191,6 @@ local function onHTTPResult(data, err)
end
end
if webSocket then
webSocket:close()
webSocket = nil
end
onCharacterList(nil, characters, account, nil)
end
@ -310,22 +320,16 @@ function EnterGame.checkWebsocket()
if webSocket then
webSocket:close()
webSocket = nil
newLogin.code:setText("")
end
return
end
if webSocket then
if webSocket.url == url then
if newLogin:isHidden() and newLogin.code:getText():len() > 1 then
newLogin:show()
newLogin:raise()
end
return
end
webSocket:close()
webSocket = nil
end
newLogin.code:setText("")
webSocket = HTTP.WebSocketJSON(url, {
onOpen = function(message, webSocketId)
if webSocket and webSocket.id == webSocketId then
@ -338,8 +342,8 @@ function EnterGame.checkWebsocket()
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)
elseif message.type == "quick_login" and message.qrcode then
EnterGame.showNewLogin(message.qrcode)
end
end
end,
@ -365,10 +369,15 @@ function EnterGame.hideNewLogin()
newLogin:hide()
end
function EnterGame.showNewLogin(code, qrcode)
function EnterGame.showNewLogin(qrcode)
if enterGame:isHidden() then return end
newLogin.code:setText(code)
newLogin.qrcode:setQRCode(qrcode, 1)
newLogin.qrcode:setQRCode("https://quath.co/0/" .. 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
newLogin:show()
newLogin:raise()
@ -449,7 +458,7 @@ function EnterGame.doLogin()
local incorrectThings = validateThings(things)
if #incorrectThings > 0 then
g_logger.info(incorrectThings)
g_logger.error(incorrectThings)
if Updater then
return Updater.updateThings(things, incorrectThings)
else

View File

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

View File

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

View File

@ -9,9 +9,9 @@ end
function updateFeatures(version)
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(GameMinimapLimitedToSingleFloor)
--g_game.enableFeature(GameMinimapLimitedToSingleFloor) -- it will generate minimap only for current floor
--g_game.enableFeature(GameSpritesAlphaChannel)
if(version >= 770) then
@ -91,6 +91,10 @@ function updateFeatures(version)
g_game.enableFeature(GameAdditionalVipInfo)
end
if(version >= 972) then
g_game.enableFeature(GameDoublePlayerGoodsMoney)
end
if(version >= 980) then
g_game.enableFeature(GamePreviewState)
g_game.enableFeature(GameClientVersion)

View File

@ -67,7 +67,12 @@ end
function bindKeys()
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:zoomOut() end, gameRootPanel)
g_keyboard.bindKeyDown('Ctrl+Q', function() tryLogout(false) end, gameRootPanel)

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.