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
83
modules/gamelib/const.lua
Normal file
@@ -0,0 +1,83 @@
|
||||
-- @docconsts @{
|
||||
|
||||
SkullNone = 0
|
||||
SkullYellow = 1
|
||||
SkullGreen = 2
|
||||
SkullWhite = 3
|
||||
SkullRed = 4
|
||||
SkullBlack = 5
|
||||
SkullOrange = 6
|
||||
|
||||
ShieldNone = 0
|
||||
ShieldWhiteYellow = 1
|
||||
ShieldWhiteBlue = 2
|
||||
ShieldBlue = 3
|
||||
ShieldYellow = 4
|
||||
ShieldBlueSharedExp = 5
|
||||
ShieldYellowSharedExp = 6
|
||||
ShieldBlueNoSharedExpBlink = 7
|
||||
ShieldYellowNoSharedExpBlink = 8
|
||||
ShieldBlueNoSharedExp = 9
|
||||
ShieldYellowNoSharedExp = 10
|
||||
|
||||
EmblemNone = 0
|
||||
EmblemGreen = 1
|
||||
EmblemRed = 2
|
||||
EmblemBlue = 3
|
||||
|
||||
North = 0
|
||||
East = 1
|
||||
South = 2
|
||||
West = 3
|
||||
NorthEast = 4
|
||||
SouthEast = 5
|
||||
SouthWest = 6
|
||||
NorthWest = 7
|
||||
|
||||
GameExtendedOpcode = 0
|
||||
GameProtocolChecksum = 1
|
||||
GameAccountNames = 2
|
||||
GameChallangeOnLogin = 3
|
||||
GameStackposOnTileAddThing = 4
|
||||
GamePenalityOnDeath = 5
|
||||
GameNameOnNpcTrade = 6
|
||||
GameDoubleFreeCapacity = 7
|
||||
GameDoubleExperience = 8
|
||||
GameTotalCapacity = 9
|
||||
GameSkillsBase = 10
|
||||
GameAdditionalPlayerStats = 11
|
||||
GameIdOnCancelAttack = 12
|
||||
GameChannelPlayerList = 13
|
||||
GamePlayerMounts = 14
|
||||
GameEnvironmentEffect = 15
|
||||
GameCreatureType = 16
|
||||
GameCreatureEmblems = 17
|
||||
GameCreaturePassableInfo = 18
|
||||
GameItemAnimationPhase = 19
|
||||
GameTrucatedPingOpcode = 20
|
||||
GameReverseCreatureStack = 21
|
||||
GameMagicEffectU16 = 22
|
||||
GamePlayerMarket = 23
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
-- @}
|
110
modules/gamelib/creature.lua
Normal file
@@ -0,0 +1,110 @@
|
||||
-- @docclass Creature
|
||||
|
||||
-- @docconsts @{
|
||||
|
||||
SkullNone = 0
|
||||
SkullYellow = 1
|
||||
SkullGreen = 2
|
||||
SkullWhite = 3
|
||||
SkullRed = 4
|
||||
SkullBlack = 5
|
||||
SkullOrange = 6
|
||||
|
||||
ShieldNone = 0
|
||||
ShieldWhiteYellow = 1
|
||||
ShieldWhiteBlue = 2
|
||||
ShieldBlue = 3
|
||||
ShieldYellow = 4
|
||||
ShieldBlueSharedExp = 5
|
||||
ShieldYellowSharedExp = 6
|
||||
ShieldBlueNoSharedExpBlink = 7
|
||||
ShieldYellowNoSharedExpBlink = 8
|
||||
ShieldBlueNoSharedExp = 9
|
||||
ShieldYellowNoSharedExp = 10
|
||||
|
||||
EmblemNone = 0
|
||||
EmblemGreen = 1
|
||||
EmblemRed = 2
|
||||
EmblemBlue = 3
|
||||
|
||||
-- @}
|
||||
|
||||
function getSkullImagePath(skullId)
|
||||
local path
|
||||
if skullId == SkullYellow then
|
||||
path = 'icons/skull_yellow.png'
|
||||
elseif skullId == SkullGreen then
|
||||
path = 'icons/skull_green.png'
|
||||
elseif skullId == SkullWhite then
|
||||
path = 'icons/skull_white.png'
|
||||
elseif skullId == SkullRed then
|
||||
path = 'icons/skull_red.png'
|
||||
elseif skullId == SkullBlack then
|
||||
path = 'icons/skull_black.png'
|
||||
elseif skullId == SkullOrange then
|
||||
path = 'icons/skull_orange.png'
|
||||
end
|
||||
path = resolvepath(path)
|
||||
return path
|
||||
end
|
||||
|
||||
function getShieldImagePathAndBlink(shieldId)
|
||||
local path
|
||||
if shieldId == ShieldWhiteYellow then
|
||||
path = 'icons/shield_yellow_white.png', false
|
||||
elseif shieldId == ShieldWhiteBlue then
|
||||
path = 'icons/shield_blue_white.png', false
|
||||
elseif shieldId == ShieldBlue then
|
||||
path = 'icons/shield_blue.png', false
|
||||
elseif shieldId == ShieldYellow then
|
||||
path = 'icons/shield_yellow.png', false
|
||||
elseif shieldId == ShieldBlueSharedExp then
|
||||
path = 'icons/shield_blue_shared.png', false
|
||||
elseif shieldId == ShieldYellowSharedExp then
|
||||
path = 'icons/shield_yellow_shared.png', false
|
||||
elseif shieldId == ShieldBlueNoSharedExpBlink then
|
||||
path = 'icons/shield_blue_not_shared.png', true
|
||||
elseif shieldId == ShieldYellowNoSharedExpBlink then
|
||||
path = 'icons/shield_yellow_not_shared.png', true
|
||||
elseif shieldId == ShieldBlueNoSharedExp then
|
||||
path = 'icons/shield_blue_not_shared.png', false
|
||||
elseif shieldId == ShieldYellowNoSharedExp then
|
||||
path = 'icons/shield_yellow_not_shared.png', false
|
||||
end
|
||||
path = resolvepath(path)
|
||||
return path
|
||||
end
|
||||
|
||||
function getEmblemImagePath(emblemId)
|
||||
local path
|
||||
if emblemId == EmblemGreen then
|
||||
path = 'icons/emblem_green.png'
|
||||
elseif emblemId == EmblemRed then
|
||||
path = 'icons/emblem_red.png'
|
||||
elseif emblemId == EmblemBlue then
|
||||
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(imagePath)
|
||||
end
|
||||
end
|
||||
|
||||
function Creature:onShieldChange(shieldId)
|
||||
local imagePath, blink = getShieldImagePathAndBlink(shieldId)
|
||||
if imagePath then
|
||||
self:setShieldTexture(imagePath, blink)
|
||||
end
|
||||
end
|
||||
|
||||
function Creature:onEmblemChange(emblemId)
|
||||
local imagePath = getEmblemImagePath(emblemId)
|
||||
if imagePath then
|
||||
self:setEmblemTexture(imagePath)
|
||||
end
|
||||
end
|
47
modules/gamelib/game.lua
Normal 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
|
||||
|
20
modules/gamelib/gamelib.otmod
Normal 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'
|
BIN
modules/gamelib/icons/emblem_blue.png
Normal file
After Width: | Height: | Size: 385 B |
BIN
modules/gamelib/icons/emblem_green.png
Normal file
After Width: | Height: | Size: 381 B |
BIN
modules/gamelib/icons/emblem_red.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
modules/gamelib/icons/shield_blue.png
Normal file
After Width: | Height: | Size: 352 B |
BIN
modules/gamelib/icons/shield_blue_not_shared.png
Normal file
After Width: | Height: | Size: 522 B |
BIN
modules/gamelib/icons/shield_blue_shared.png
Normal file
After Width: | Height: | Size: 516 B |
BIN
modules/gamelib/icons/shield_blue_white.png
Normal file
After Width: | Height: | Size: 404 B |
BIN
modules/gamelib/icons/shield_yellow.png
Normal file
After Width: | Height: | Size: 377 B |
BIN
modules/gamelib/icons/shield_yellow_not_shared.png
Normal file
After Width: | Height: | Size: 512 B |
BIN
modules/gamelib/icons/shield_yellow_shared.png
Normal file
After Width: | Height: | Size: 494 B |
BIN
modules/gamelib/icons/shield_yellow_white.png
Normal file
After Width: | Height: | Size: 407 B |
BIN
modules/gamelib/icons/skull_black.png
Normal file
After Width: | Height: | Size: 482 B |
BIN
modules/gamelib/icons/skull_green.png
Normal file
After Width: | Height: | Size: 438 B |
BIN
modules/gamelib/icons/skull_orange.png
Normal file
After Width: | Height: | Size: 445 B |
BIN
modules/gamelib/icons/skull_red.png
Normal file
After Width: | Height: | Size: 421 B |
BIN
modules/gamelib/icons/skull_white.png
Normal file
After Width: | Height: | Size: 437 B |
BIN
modules/gamelib/icons/skull_yellow.png
Normal file
After Width: | Height: | Size: 437 B |
38
modules/gamelib/market.lua
Normal file
@@ -0,0 +1,38 @@
|
||||
MarketAction = {
|
||||
Buy = 0,
|
||||
Sell = 1
|
||||
}
|
||||
|
||||
MarketRequest = {
|
||||
MyOffers = 0xFFFE,
|
||||
MyHistory = 0xFFFF
|
||||
}
|
||||
|
||||
MarketOfferState = {
|
||||
Active = 0,
|
||||
Cancelled = 1,
|
||||
Expired = 2,
|
||||
Accepted = 3,
|
||||
AcceptedEx = 255
|
||||
}
|
||||
|
||||
MarketItemDescription = {
|
||||
Armor = 1,
|
||||
Attack = 2,
|
||||
Container = 3,
|
||||
Defense = 4,
|
||||
General = 5,
|
||||
DecayTime = 6,
|
||||
Combat = 7,
|
||||
MinLevel = 8,
|
||||
MinMagicLevel = 9,
|
||||
Vocation = 10,
|
||||
Rune = 11,
|
||||
Ability = 12,
|
||||
Charges = 13,
|
||||
WeaponName = 14,
|
||||
Weight = 15,
|
||||
|
||||
First = Armor,
|
||||
Last = Weight
|
||||
}
|
54
modules/gamelib/player.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
-- @docclass Player
|
||||
|
||||
InventorySlotHead = 1
|
||||
InventorySlotNeck = 2
|
||||
InventorySlotBack = 3
|
||||
InventorySlotBody = 4
|
||||
InventorySlotRight = 5
|
||||
InventorySlotLeft = 6
|
||||
InventorySlotLeg = 7
|
||||
InventorySlotFeet = 8
|
||||
InventorySlotFinger = 9
|
||||
InventorySlotAmmo = 10
|
||||
|
||||
InventorySlotFirst = 1
|
||||
InventorySlotLast = 10
|
||||
|
||||
function Player:isPartyLeader()
|
||||
local shield = self:getShield()
|
||||
return (shield == ShieldWhiteYellow or
|
||||
shield == ShieldYellow or
|
||||
shield == ShieldYellowSharedExp or
|
||||
shield == ShieldYellowNoSharedExpBlink or
|
||||
shield == ShieldYellowNoSharedExp)
|
||||
end
|
||||
|
||||
function Player:isPartyMember()
|
||||
local shield = self:getShield()
|
||||
return (shield == ShieldWhiteYellow or
|
||||
shield == ShieldYellow or
|
||||
shield == ShieldYellowSharedExp or
|
||||
shield == ShieldYellowNoSharedExpBlink or
|
||||
shield == ShieldYellowNoSharedExp or
|
||||
shield == ShieldBlueSharedExp or
|
||||
shield == ShieldBlueNoSharedExpBlink or
|
||||
shield == ShieldBlueNoSharedExp or
|
||||
shield == ShieldBlue)
|
||||
end
|
||||
|
||||
function Player:isPartySharedExperienceActive()
|
||||
local shield = self:getShield()
|
||||
return (shield == ShieldYellowSharedExp or
|
||||
shield == ShieldYellowNoSharedExpBlink or
|
||||
shield == ShieldYellowNoSharedExp or
|
||||
shield == ShieldBlueSharedExp or
|
||||
shield == ShieldBlueNoSharedExpBlink or
|
||||
shield == ShieldBlueNoSharedExp)
|
||||
end
|
||||
|
||||
function Player:hasVip(creatureName)
|
||||
for id, vip in pairs(g_game.getVips()) do
|
||||
if (vip[1] == creatureName) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
23
modules/gamelib/protocolgame.lua
Normal 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
|
135
modules/gamelib/protocollogin.lua
Normal file
@@ -0,0 +1,135 @@
|
||||
-- @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(g_game.getOsType())
|
||||
msg:addU16(g_game.getProtocolVersion())
|
||||
|
||||
msg:addU32(g_things.getDatSignature())
|
||||
msg:addU32(g_sprites.getSprSignature())
|
||||
msg:addU32(PIC_SIGNATURE)
|
||||
|
||||
local paddingBytes = 128
|
||||
msg:addU8(0) -- first RSA byte must be 0
|
||||
paddingBytes = paddingBytes - 1
|
||||
|
||||
-- xtea key
|
||||
protocol:generateXteaKey()
|
||||
local xteaKey = protocol:getXteaKey()
|
||||
msg:addU32(xteaKey[1])
|
||||
msg:addU32(xteaKey[2])
|
||||
msg:addU32(xteaKey[3])
|
||||
msg:addU32(xteaKey[4])
|
||||
paddingBytes = paddingBytes - 16
|
||||
|
||||
if g_game.getFeature(GameProtocolChecksum) then
|
||||
protocol:enableChecksum()
|
||||
end
|
||||
|
||||
if g_game.getFeature(GameAccountNames) then
|
||||
msg:addString(protocol.accountName)
|
||||
msg:addString(protocol.accountPassword)
|
||||
paddingBytes = paddingBytes - (4 + string.len(protocol.accountName) + string.len(protocol.accountPassword))
|
||||
else
|
||||
msg:addU32(tonumber(protocol.accountName))
|
||||
msg:addString(protocol.accountPassword)
|
||||
paddingBytes = paddingBytes - (6 + string.len(protocol.accountPassword))
|
||||
end
|
||||
|
||||
msg:addPaddingBytes(paddingBytes, 0)
|
||||
msg:encryptRsa(128, g_game.getRsa())
|
||||
|
||||
protocol:send(msg)
|
||||
protocol:enableXteaEncryption()
|
||||
protocol:recv()
|
||||
end
|
||||
|
||||
-- events
|
||||
function ProtocolLogin:onConnect()
|
||||
self:connectCallback(self)
|
||||
end
|
||||
|
||||
function ProtocolLogin:onRecv(msg)
|
||||
while not msg:eof() do
|
||||
local opcode = msg:getU8()
|
||||
if opcode == LoginServerError then
|
||||
self:parseError(msg)
|
||||
elseif opcode == LoginServerMotd then
|
||||
self:parseMotd(msg)
|
||||
elseif opcode == LoginServerUpdateNeeded then
|
||||
signalcall(self.onError, self, tr("Client needs update."))
|
||||
elseif opcode == LoginServerCharacterList then
|
||||
self:parseCharacterList(msg)
|
||||
else
|
||||
self:parseOpcode(opcode, msg)
|
||||
end
|
||||
end
|
||||
self:disconnect()
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function ProtocolLogin.create()
|
||||
return ProtocolLogin.internalCreate()
|
||||
end
|
||||
|
||||
function ProtocolLogin:login(host, port, accountName, accountPassword)
|
||||
if string.len(accountName) == 0 or string.len(accountPassword) == 0 then
|
||||
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
|
||||
|
||||
self.accountName = accountName
|
||||
self.accountPassword = accountPassword
|
||||
self.connectCallback = sendLoginPacket
|
||||
|
||||
self:connect(host, port)
|
||||
end
|
||||
|
||||
function ProtocolLogin:cancelLogin()
|
||||
self:disconnect()
|
||||
end
|
||||
|
||||
function ProtocolLogin:parseError(msg)
|
||||
local errorMessage = msg:getString()
|
||||
signalcall(self.onError, self, errorMessage)
|
||||
end
|
||||
|
||||
function ProtocolLogin:parseMotd(msg)
|
||||
local motd = msg:getString()
|
||||
signalcall(self.onMotd, self, motd)
|
||||
end
|
||||
|
||||
function ProtocolLogin:parseCharacterList(msg)
|
||||
local characters = {}
|
||||
local charactersCount = msg:getU8()
|
||||
for i=1,charactersCount do
|
||||
local character = {}
|
||||
character[1] = msg:getString()
|
||||
character[2] = msg:getString()
|
||||
character[3] = iptostring(msg:getU32())
|
||||
character[4] = msg:getU16()
|
||||
characters[i] = character
|
||||
end
|
||||
local premDays = msg:getU16()
|
||||
signalcall(self.onCharacterList, self, characters, premDays)
|
||||
end
|
||||
|
||||
function ProtocolLogin:parseOpcode(opcode, msg)
|
||||
signalcall(self.onOpcode, self, opcode, msg)
|
||||
end
|