Multi-protocol

Lots of chagnes to add multi protocol flexibility, not really
completed yet, still have to rework text messages opcodes and other stuff,
so this still a working in progress feature

* Rework dat reader, the dat reader can now
* dinamically detect dat version
* Split game into gamelib and game_interface
* Lots of other minor changes
This commit is contained in:
Eduardo Bart
2012-07-17 20:49:21 -03:00
parent 6fc11d2fa9
commit eb24d6776e
64 changed files with 536 additions and 588 deletions

View File

@@ -16,7 +16,6 @@ Module
- client_terminal
- client_modulemanager
- client_entergame
- game
@onLoad: |
dofile 'client'

View File

@@ -11,7 +11,6 @@ function Background.init()
local clientVersionLabel = background:getChildById('clientVersionLabel')
clientVersionLabel:setText('OTClient ' .. g_app.getVersion() .. '\n' ..
'Rev ' .. g_app.getBuildRevision() .. ' ('.. g_app.getBuildCommit() .. ')\n' ..
'Protocol ' .. g_game.getProtocolVersion() .. '\n' ..
'Built on ' .. g_app.getBuildDate())
if not g_game.isOnline() then

View File

@@ -54,7 +54,7 @@ local function updateWait(timeStart, timeEnd)
progressBar:setPercent(percent)
local label = waitingWindow:getChildById('timeLabel')
label:setText('Trying to reconnect in ' .. timeStr .. ' seconds.')
label:setText(tr('Trying to reconnect in %s seconds.', timeStr))
updateWaitEvent = scheduleEvent(function() updateWait(timeStart, timeEnd) end, 1000 * progressBar:getPercentPixels() / 100 * (timeEnd - timeStart))
return true

View File

@@ -5,6 +5,7 @@ local loadBox
local enterGame
local motdButton
local enterGameButton
local protocolBox
-- private functions
local function onError(protocol, message, errorCode)
@@ -66,6 +67,11 @@ function EnterGame.init()
local host = g_settings.get('host')
local port = g_settings.get('port')
local autologin = g_settings.getBoolean('autologin')
local protocol = g_settings.getInteger('protocol', 860)
if not protocol or protocol == 0 then
protocol = 860
end
if port == nil or port == 0 then port = 7171 end
@@ -77,6 +83,12 @@ function EnterGame.init()
enterGame:getChildById('rememberPasswordBox'):setChecked(#account > 0)
enterGame:getChildById('accountNameTextEdit'):focus()
protocolBox = enterGame:getChildById('protocolComboBox')
for _i,proto in pairs(g_game.getSupportedProtocols()) do
protocolBox:addOption(proto)
end
protocolBox:setCurrentOption(protocol)
-- only open entergame when app starts
if not g_app.isRunning() then
if #host > 0 and #password > 0 and #account > 0 and autologin then
@@ -95,6 +107,7 @@ function EnterGame.terminate()
enterGameButton = nil
motdButton:destroy()
motdButton = nil
protocolBox = nil
EnterGame = nil
end
@@ -116,7 +129,6 @@ function EnterGame.openWindow()
end
end
function EnterGame.clearAccountFields()
enterGame:getChildById('accountNameTextEdit'):clearText()
enterGame:getChildById('accountPasswordTextEdit'):clearText()
@@ -130,16 +142,18 @@ function EnterGame.doLogin()
G.password = enterGame:getChildById('accountPasswordTextEdit'):getText()
G.host = enterGame:getChildById('serverHostTextEdit'):getText()
G.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText())
local protocol = tonumber(protocolBox:getText())
EnterGame.hide()
if G.host == '' or G.port == nil or G.port == 0 then
local errorBox = displayErrorBox(tr('Login Error'), tr('Enter a valid server host and port to login.'))
if g_game.isOnline() then
local errorBox = displayErrorBox(tr('Login Error'), tr('Cannot login while already in game.'))
connect(errorBox, { onOk = EnterGame.show })
return
end
g_settings.set('host', G.host)
g_settings.set('port', G.port)
g_settings.set('protocol', protocol)
local protocolLogin = ProtocolLogin.create()
protocolLogin.onError = onError
@@ -153,6 +167,8 @@ function EnterGame.doLogin()
EnterGame.show()
end })
g_game.chooseRsa(G.host)
g_game.setProtocolVersion(protocol)
protocolLogin:login(G.host, G.port, G.account, G.password)
end

View File

@@ -1,7 +1,7 @@
MainWindow
id: enterGame
!text: tr('Enter Game')
size: 236 240
size: 236 274
@onEnter: EnterGame.doLogin()
@onEscape: EnterGame.hide()
@@ -43,26 +43,43 @@ MainWindow
TextEdit
id: serverHostTextEdit
!tooltip: tr('Make sure that your client uses\nthe correct game protocol version')
anchors.left: serverLabel.left
anchors.left: parent.left
anchors.right: parent.right
anchors.top: serverLabel.bottom
margin-top: 2
width: 140
Label
id: protocolLabel
!text: tr('Protocol')
anchors.left: parent.left
anchors.top: serverHostTextEdit.bottom
anchors.right: portLabel.left
margin-right: 10
margin-top: 8
ComboBox
id: protocolComboBox
anchors.left: protocolLabel.left
anchors.right: protocolLabel.right
anchors.top: protocolLabel.bottom
margin-top: 2
width: 90
Label
id: portLabel
!text: tr('Port')
anchors.left: serverHostTextEdit.right
anchors.top: serverLabel.top
margin-left: 10
text-auto-resize: true
anchors.right: parent.right
anchors.top: serverHostTextEdit.bottom
margin-top: 8
width: 70
TextEdit
id: serverPortTextEdit
text: 7171
anchors.right: parent.right
anchors.left: portLabel.left
anchors.top: portLabel.bottom
margin-top: 2
width: 55
CheckBox
id: rememberPasswordBox

View File

@@ -55,4 +55,4 @@ function Extended.unregister(opcode)
callbacks[opcode] = nil
return true
end
end

View File

@@ -132,6 +132,7 @@ function getfsrcpath(depth)
end
function resolvepath(filePath, depth)
if not filePath then return nil end
depth = depth or 1
if filePath then
if filePath:sub(0, 1) ~= '/' then

View File

@@ -1,44 +0,0 @@
Module
name: game
description: Contains game related classes
author: OTClient team
website: www.otclient.info
dependencies:
- client_extended
- client_background
- game_tibiafiles
load-later:
- game_interface
- game_hotkeys
- game_questlog
- game_textmessage
- game_console
- game_outfit
- game_healthinfo
- game_skills
- game_inventory
- game_combatcontrols
- game_containers
- game_viplist
- game_battle
- game_minimap
- game_npctrade
- game_textwindow
- game_playertrade
- game_ruleviolation
- game_bugreport
- game_shaders
- game_playerdeath
- game_playermount
- game_market
@onLoad: |
dofile 'const'
dofile 'protocollogin'
dofile 'creature'
dofile 'player'
dofile 'market'

View File

@@ -224,7 +224,7 @@ function Battle.checkCreatureSkull(creature, skullId)
if creature:getSkull() ~= SkullNone then
skullWidget:setWidth(skullWidget:getHeight())
local imagePath = getSkullImagePath(creature:getSkull())
skullWidget:setImageSource('/game/' .. imagePath)
skullWidget:setImageSource(imagePath)
labelWidget:setMarginLeft(5)
else
skullWidget:setWidth(0)
@@ -246,7 +246,7 @@ function Battle.checkCreatureEmblem(creature, emblemId)
if emblemId ~= EmblemNone then
emblemWidget:setWidth(emblemWidget:getHeight())
local imagePath = getEmblemImagePath(emblemId)
emblemWidget:setImageSource('/game/' .. imagePath)
emblemWidget:setImageSource(imagePath)
emblemWidget:setMarginLeft(5)
labelWidget:setMarginLeft(5)
else

View File

@@ -4,6 +4,30 @@ Module
author: OTClient team
website: www.otclient.info
load-later:
- game_hotkeys
- game_questlog
- game_textmessage
- game_console
- game_outfit
- game_healthinfo
- game_skills
- game_inventory
- game_combatcontrols
- game_containers
- game_viplist
- game_battle
- game_minimap
- game_npctrade
- game_textwindow
- game_playertrade
- game_ruleviolation
- game_bugreport
- game_shaders
- game_playerdeath
- game_playermount
- game_market
@onLoad: |
dofile 'widgets/uigamemap'
dofile 'widgets/uiitem'

View File

@@ -4,9 +4,6 @@ Module
author: BeniS
website: www.otclient.info
dependencies:
- game
@onLoad: |
dofile 'marketoffer'
dofile 'marketprotocol'

View File

@@ -32,7 +32,7 @@ MarketOffer.new = function(offerId, action, itemId, amount, price, playerName, s
offer.player = playerName
state = tonumber(state)
if state ~= MarketOfferState.Active and state ~= MarketOfferState.Cancelled
if state ~= MarketOfferState.Active and state ~= MarketOfferState.Cancelled
and state ~= MarketOfferState.Expired and state ~= MarketOfferState.Accepted then
g_logger.error('MarketOffer.new - invalid state provided.')
end

View File

@@ -3,27 +3,6 @@ MarketProtocol = {}
local market
-- private functions
local function parseOpcode(protocol, opcode, msg)
if not g_game.getFeature(GamePlayerMarket) then
return false
end
-- process msg
if opcode == GameServerOpcodes.GameServerMarketEnter then
parseMarketEnter(msg)
elseif opcode == GameServerOpcodes.GameServerMarketLeave then
parseMarketLeave(msg)
elseif opcode == GameServerOpcodes.GameServerMarketDetail then
parseMarketDetail(msg)
elseif opcode == GameServerOpcodes.GameServerMarketBrowse then
parseMarketBrowse(msg)
else
return false
end
return true
end
local function send(msg)
print(msg:getMessageSize())
g_game.getProtocolGame():safeSend(msg)
@@ -49,12 +28,11 @@ local function readMarketOffer(msg, action, var)
else
playerName = msg:getString()
end
return MarketOffer.new({timestamp, counter}, action, itemId, amount, price, playerName, state)
end
-- parsing protocols
local function parseMarketEnter(msg)
local balance = msg:getU32()
local offers = msg:getU8()
@@ -128,14 +106,18 @@ local function parseMarketBrowse(msg)
end
-- public functions
function MarketProtocol.init()
connect(ProtocolGame, { onOpcode = parseOpcode } )
ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketEnter, parseMarketEnter)
ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave)
ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail)
ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse)
end
function MarketProtocol.terminate()
disconnect(ProtocolGame, { onOpcode = parseOpcode } )
ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketEnter, parseMarketEnter)
ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave)
ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail)
ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse)
market = nil
MarketProtocol = nil

View File

@@ -34,11 +34,6 @@ SouthEast = 5
SouthWest = 6
NorthWest = 7
LoginServerError = 10
LoginServerMotd = 20
LoginServerUpdateNeeded = 30
LoginServerCharacterList = 100
GameExtendedOpcode = 0
GameProtocolChecksum = 1
GameAccountNames = 2
@@ -56,7 +51,7 @@ GameChannelPlayerList = 13
GamePlayerMounts = 14
GameEnvironmentEffect = 15
GameCreatureType = 16
GameCreatureAdditionalInfo = 17
GameCreatureEmblems = 17
GameCreaturePassableInfo = 18
GameItemAnimationPhase = 19
GameTrucatedPingOpcode = 20
@@ -64,6 +59,25 @@ GameReverseCreatureStack = 21
GameMagicEffectU16 = 22
GamePlayerMarket = 23
OTSERV_RSA = "109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110603907230888610072655818825358503429057592827629436413108566029093628212635953836686562675849720620786279431090218017681061521755056710823876476444260558147179707119674283982419152118103759076030616683978566631413"
OTSERV_RSA = "1091201329673994292788609605089955415282375029027981291234687579" ..
"3726629149257644633073969600111060390723088861007265581882535850" ..
"3429057592827629436413108566029093628212635953836686562675849720" ..
"6207862794310902180176810615217550567108238764764442605581471797" ..
"07119674283982419152118103759076030616683978566631413"
-- @}
CIPSOFT_RSA = "1321277432058722840622950990822933849527763264961655079678763618" ..
"4334395343554449668205332383339435179772895415509701210392836078" ..
"6959821132214473291575712138800495033169914814069637740318278150" ..
"2907336840325241747827401343576296990629870233111328210165697754" ..
"88792221429527047321331896351555606801473202394175817"
OsTypes = {
Linux = 1,
Windows = 2,
Flash = 3,
OtclientLinux = 10,
OtclientWindows = 11,
OtclientMac = 12
}
-- @}

View File

@@ -30,73 +30,81 @@ EmblemBlue = 3
-- @}
function getSkullImagePath(skullId)
local path
if skullId == SkullYellow then
return 'icons/skull_yellow.png'
path = 'icons/skull_yellow.png'
elseif skullId == SkullGreen then
return 'icons/skull_green.png'
path = 'icons/skull_green.png'
elseif skullId == SkullWhite then
return 'icons/skull_white.png'
path = 'icons/skull_white.png'
elseif skullId == SkullRed then
return 'icons/skull_red.png'
path = 'icons/skull_red.png'
elseif skullId == SkullBlack then
return 'icons/skull_black.png'
path = 'icons/skull_black.png'
elseif skullId == SkullOrange then
return 'icons/skull_orange.png'
path = 'icons/skull_orange.png'
end
path = resolvepath(path)
return path
end
function getShieldImagePathAndBlink(shieldId)
local path
if shieldId == ShieldWhiteYellow then
return 'icons/shield_yellow_white.png', false
path = 'icons/shield_yellow_white.png', false
elseif shieldId == ShieldWhiteBlue then
return 'icons/shield_blue_white.png', false
path = 'icons/shield_blue_white.png', false
elseif shieldId == ShieldBlue then
return 'icons/shield_blue.png', false
path = 'icons/shield_blue.png', false
elseif shieldId == ShieldYellow then
return 'icons/shield_yellow.png', false
path = 'icons/shield_yellow.png', false
elseif shieldId == ShieldBlueSharedExp then
return 'icons/shield_blue_shared.png', false
path = 'icons/shield_blue_shared.png', false
elseif shieldId == ShieldYellowSharedExp then
return 'icons/shield_yellow_shared.png', false
path = 'icons/shield_yellow_shared.png', false
elseif shieldId == ShieldBlueNoSharedExpBlink then
return 'icons/shield_blue_not_shared.png', true
path = 'icons/shield_blue_not_shared.png', true
elseif shieldId == ShieldYellowNoSharedExpBlink then
return 'icons/shield_yellow_not_shared.png', true
path = 'icons/shield_yellow_not_shared.png', true
elseif shieldId == ShieldBlueNoSharedExp then
return 'icons/shield_blue_not_shared.png', false
path = 'icons/shield_blue_not_shared.png', false
elseif shieldId == ShieldYellowNoSharedExp then
return 'icons/shield_yellow_not_shared.png', false
path = 'icons/shield_yellow_not_shared.png', false
end
path = resolvepath(path)
return path
end
function getEmblemImagePath(emblemId)
local path
if emblemId == EmblemGreen then
return 'icons/emblem_green.png'
path = 'icons/emblem_green.png'
elseif emblemId == EmblemRed then
return 'icons/emblem_red.png'
path = 'icons/emblem_red.png'
elseif emblemId == EmblemBlue then
return 'icons/emblem_blue.png'
path = 'icons/emblem_blue.png'
end
path = resolvepath(path)
return path
end
function Creature:onSkullChange(skullId)
local imagePath = getSkullImagePath(skullId)
if imagePath then
self:setSkullTexture(resolvepath(imagePath))
self:setSkullTexture(imagePath)
end
end
function Creature:onShieldChange(shieldId)
local imagePath, blink = getShieldImagePathAndBlink(shieldId)
if imagePath then
self:setShieldTexture(resolvepath(imagePath), blink)
self:setShieldTexture(imagePath, blink)
end
end
function Creature:onEmblemChange(emblemId)
local imagePath = getEmblemImagePath(emblemId)
if imagePath then
self:setEmblemTexture(resolvepath(imagePath))
self:setEmblemTexture(imagePath)
end
end

47
modules/gamelib/game.lua Normal file
View File

@@ -0,0 +1,47 @@
local currentRsa = OTSERV_RSA
function g_game.getRsa()
return currentRsa
end
function g_game.chooseRsa(host)
if host:match('.*\.tibia\.com') or host:match('.*\.cipsoft\.com') then
currentRsa = CIPSOFT_RSA
else
currentRsa = OTSERV_RSA
end
end
function g_game.setRsa(rsa)
currentRsa = rsa
end
function g_game.isOfficialTibia()
return currentRsa == CIPSOFT_RSA
end
function g_game.getOsType()
if g_game.isOfficialTibia() then
if g_app.getOs() == 'windows' then
return OsTypes.Windows
else
return OsTypes.Linux
end
else
if g_app.getOs() == 'windows' then
return OsTypes.OtclientWindows
elseif g_app.getOs() == 'mac' then
return OsTypes.OtclientMac
else
return OsTypes.OtclientLinux
end
end
end
function g_game.getSupportedProtocols()
return {
810, 853, 854, 860, 861, 862, 870, 940,
953, 954, 960
}
end

View File

@@ -0,0 +1,20 @@
Module
name: gamelib
description: Contains game related classes
author: OTClient team
website: www.otclient.info
dependencies:
- client_extended
- game_tibiafiles
@onLoad: |
dofile 'const'
dofile 'protocollogin'
dofile 'protocolgame'
dofile 'game'
dofile 'creature'
dofile 'player'
dofile 'market'

View File

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

View File

Before

Width:  |  Height:  |  Size: 381 B

After

Width:  |  Height:  |  Size: 381 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 352 B

After

Width:  |  Height:  |  Size: 352 B

View File

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 522 B

View File

Before

Width:  |  Height:  |  Size: 516 B

After

Width:  |  Height:  |  Size: 516 B

View File

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 404 B

View File

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 377 B

View File

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View File

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 494 B

View File

Before

Width:  |  Height:  |  Size: 407 B

After

Width:  |  Height:  |  Size: 407 B

View File

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 482 B

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

Before

Width:  |  Height:  |  Size: 445 B

After

Width:  |  Height:  |  Size: 445 B

View File

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 421 B

View File

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View File

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View File

@@ -52,4 +52,3 @@ function Player:hasVip(creatureName)
end
return false
end

View File

@@ -0,0 +1,23 @@
local opcodeCallbacks = {}
function ProtocolGame:onOpcode(opcode, msg)
for i, callback in pairs(opcodeCallbacks) do
if i == opcode then
callback(msg)
return true
end
end
return false
end
function ProtocolGame.registerOpcode(opcode, callback)
if opcodeCallbacks[opcode] then
error('opcode ' .. opcode .. ' already registered will be overriden')
end
opcodeCallbacks[opcode] = callback
end
function ProtocolGame.unregisterOpcode(opcode)
opcodeCallbacks[opcode] = nil
end

View File

@@ -1,16 +1,25 @@
-- @docclass
ProtocolLogin = extends(Protocol)
-- set to the latest Tibia.pic signature to make otclient compatible with official tibia
local PIC_SIGNATURE = 0
LoginServerError = 10
LoginServerMotd = 20
LoginServerUpdateNeeded = 30
LoginServerCharacterList = 100
-- private functions
local function sendLoginPacket(protocol)
local msg = OutputMessage.create()
msg:addU8(ClientOpcodes.ClientEnterAccount)
msg:addU16(1) -- todo: ClientOs
msg:addU16(g_game.getClientVersion())
msg:addU16(g_game.getOsType())
msg:addU16(g_game.getProtocolVersion())
msg:addU32(g_things.getDatSignature())
msg:addU32(g_sprites.getSprSignature())
msg:addU32(0) -- todo: pic signature
msg:addU32(PIC_SIGNATURE)
local paddingBytes = 128
msg:addU8(0) -- first RSA byte must be 0
@@ -40,7 +49,7 @@ local function sendLoginPacket(protocol)
end
msg:addPaddingBytes(paddingBytes, 0)
msg:encryptRSA(128, OTSERV_RSA) -- todo: check whether to use cip or ot rsa
msg:encryptRsa(128, g_game.getRsa())
protocol:send(msg)
protocol:enableXteaEncryption()
@@ -60,7 +69,7 @@ function ProtocolLogin:onRecv(msg)
elseif opcode == LoginServerMotd then
self:parseMotd(msg)
elseif opcode == LoginServerUpdateNeeded then
signalcall(self.onError, self, "Client needs update.")
signalcall(self.onError, self, tr("Client needs update."))
elseif opcode == LoginServerCharacterList then
self:parseCharacterList(msg)
else
@@ -77,7 +86,11 @@ end
function ProtocolLogin:login(host, port, accountName, accountPassword)
if string.len(accountName) == 0 or string.len(accountPassword) == 0 then
signalcall(self.onError, self, "You must enter an account name and password.")
signalcall(self.onError, self, tr("You must enter an account name and password."))
return
end
if string.len(host) == 0 or port == nil or port == 0 then
signalcall(self.onError, self, tr("You must enter a valid server address and port."))
return
end