• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

OTClient decoding protocol userdata

zbizu

Legendary OT User
Joined
Nov 22, 2010
Messages
3,323
Solutions
26
Reaction score
2,690
Location
Poland
Hello.

I'm trying to obtain server info for otclient through Protocol methods.
Example in php: renatorib/otinfo (https://github.com/renatorib/otinfo)

my code is:
Lua:
HOST = '127.0.0.1'
PORT = 7171

function sendTestingRequest()
    if not HOST then
        return
    end
 
    local protocol = Protocol.create()
    protocol.onConnect = onConnect
    protocol.onRecv = onRecv
    --protocol.onError = onError
    protocol:connect(HOST, PORT)

end

function onConnect(protocol)
    -- print in terminal
    pinfo(tostring(protocol))
    pinfo("connected")
 
    local msg = OutputMessage.create()
    msg:addString("\6")
    msg:addString("\0")
    msg:addString("\255")
    msg:addString("\255")
    protocol:send(msg)
    protocol:recv()
end

function onRecv(message)
    pinfo("received")
    pinfo(tostring(message))
 
    for k, v in pairs(getmetatable(message)) do
        pinfo(k, v)
    end
    --protocol:disconnect()
end

The terminal output is:
Code:
Loaded module 'test'
> sendTestingRequest()
userdata: 0x04ee9d38
connected
received
userdata: 0x04eea028
__newindex
fieldmethods
__eq
methods
__gc
__index

my question is: how do I obtain string from received userdata?
I don't mind source edits if necessary.
 
Solution
message structure as you described, tried both 255 and 0xFF

using port 7171:
Code:
Your connection has been lost.
Either your network or the server went down. (ERROR 2)

using port 7173 (same error as when sending when my server is offline):
Code:
Nie mo¿na nawi¹zaæ po³¹czenia, poniewa¿ komputer docelowy aktywnie go odmawia
Connection failed. (ERROR 10061)

(translation of above: unable to connect, connection refused)
My bad, it's 7171. Anyway, this will work only outside OTC Protocol communication (like PHP).

I did some tests and here is what will work for you.
Lua:
msg:addU8(255)
msg:addU8(1)
msg:addU8(8) -- this is important
This 8 is a byte based on
C++:
enum RequestedInfo_t : uint16_t {
    REQUEST_BASIC_SERVER_INFO...
ok, my bad, it should have two arguments
Code:
onRecv(protocol, message)
    for k, v in pairs(getmetatable(message.methods)) do
        pinfo(k, v)
    end

but how do I obtain the message now? When I call :getString() I'm getting an error about reaching eof.

edit:
ok I managed to decode it through
Code:
    for i = 1, 40 do
    pinfo(("").char(message:getU8()))
    end

but I'm getting "only clients with protocol 10.98 allowed" message. How do I change the message I'm sending to receive server info?
 
Last edited:
Why are you sending protocol message through http socket? Send a string, json string preferably, not "OutputMessage"
 
Because I want to ask multiple servers about their name, motd and players online and I want to send a standard packet to get xml response.

I tried to write my own protocol based on protocollogin.lua, but it doesn't work.
code:
Lua:
-- @docclass
ProtocolStatus = extends(Protocol, "ProtocolStatus")

function ProtocolStatus:login(host, port)
    pinfo("login")
   
    if string.len(host) == 0 or port == nil or port == 0 then
        signalcall(self.onStatusError, self, tr("You must enter a valid server address and port."))
        return
    end

    self.connectCallback = self.sendStatusPacket
    self:connect(host, port)
end

function ProtocolStatus:cancelLogin()
    pinfo("cancelLogin")
    self:disconnect()
end

function ProtocolStatus:sendStatusPacket()
    pinfo("sendStatusPacket")
    local msg = OutputMessage.create()
    msg:addU8(0xFF)
    msg:addString("info")

    self:send(msg)
    pinfo("sent")
    self:recv()
end

function ProtocolStatus:onConnect()
    pinfo("onConnect")
    self.gotConnection = true
    self:connectCallback()
    self.connectCallback = nil
end

function ProtocolStatus:onRecv(msg)
    pinfo("onRecv")
    while not msg:eof() do
        local opcode = msg:getU8()
        pinfo(opcode)
    end
    self:disconnect()
end

function ProtocolStatus:parseError(msg)
    pinfo("parseError")
     
    local errorMessage = msg:getString()
    pinfo(errorMessage)
    signalcall(self.onStatusError, self, errorMessage)
end

function ProtocolStatus:onError(msg, code)
    local text = translateNetworkError(code, self:isConnecting(), msg)
    pinfo("onError:")
    pinfo(msg)
    pinfo("onError2:")

    pinfo(text)
 
    signalcall(self.onStatusError, self, text)
end

function testRequest()
    protocolStatus = ProtocolStatus.create()
    protocolStatus.onStatusError = onError
    protocolStatus:login("127.0.0.1", 7171)
end

function called in terminal:
Code:
testRequest()

output:
Code:
login
onConnect
sendStatusPacket
sent
onError:
End of file
onError2:
Your connection has been lost.
Either your network or the server went down. (ERROR 2)

for context: my localhost server is online and php script I linked retreives that data correctly
 
If you want info about server then connect using server info port (7171 by default) and send
Code:
msg:addU8(255)
msg:addU8(255)
msg:addString("info")
Response will be entire data about server, players online, max players, world name, motd, owner etc.
 
Last edited:
message structure as you described, tried both 255 and 0xFF

using port 7171:
Code:
Your connection has been lost.
Either your network or the server went down. (ERROR 2)

using port 7173 (same error as when sending when my server is offline):
Code:
Nie mo¿na nawi¹zaæ po³¹czenia, poniewa¿ komputer docelowy aktywnie go odmawia
Connection failed. (ERROR 10061)

(translation of above: unable to connect, connection refused)
 
message structure as you described, tried both 255 and 0xFF

using port 7171:
Code:
Your connection has been lost.
Either your network or the server went down. (ERROR 2)

using port 7173 (same error as when sending when my server is offline):
Code:
Nie mo¿na nawi¹zaæ po³¹czenia, poniewa¿ komputer docelowy aktywnie go odmawia
Connection failed. (ERROR 10061)

(translation of above: unable to connect, connection refused)
My bad, it's 7171. Anyway, this will work only outside OTC Protocol communication (like PHP).

I did some tests and here is what will work for you.
Lua:
msg:addU8(255)
msg:addU8(1)
msg:addU8(8) -- this is important
This 8 is a byte based on
C++:
enum RequestedInfo_t : uint16_t {
    REQUEST_BASIC_SERVER_INFO = 1 << 0,
    REQUEST_OWNER_SERVER_INFO = 1 << 1,
    REQUEST_MISC_SERVER_INFO = 1 << 2,
    REQUEST_PLAYERS_INFO = 1 << 3,
    REQUEST_MAP_INFO = 1 << 4,
    REQUEST_EXT_PLAYERS_INFO = 1 << 5,
    REQUEST_PLAYER_STATUS_INFO = 1 << 6,
    REQUEST_SERVER_SOFTWARE_INFO = 1 << 7,
};
So 8 = REQUEST_PLAYERS_INFO and this will return Online Players, Max Players, Online Record
Lua:
function ProtocolStatus:onRecv(msg)
  while not msg:eof() do
    local opcode = msg:getU8()
    if opcode == 32 then -- REQUEST_PLAYERS_INFO
      print(msg:getU32()) -- Online Players
      print(msg:getU32()) -- Max Players
      print(msg:getU32()) -- Online Record
    end
  end
  self:disconnect()
end
You can add flags (like msg:addByte(2 + 8)) if you want more info in one message.

You can check what each flag adds to output message here.
C++:
void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string& characterName)
{
    auto output = OutputMessagePool::getOutputMessage();

    if (requestedInfo & REQUEST_BASIC_SERVER_INFO) {
        output->addByte(0x10);
        output->addString(g_config.getString(ConfigManager::SERVER_NAME));
        output->addString(g_config.getString(ConfigManager::IP));
        output->addString(std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT)));
    }

    if (requestedInfo & REQUEST_OWNER_SERVER_INFO) {
        output->addByte(0x11);
        output->addString(g_config.getString(ConfigManager::OWNER_NAME));
        output->addString(g_config.getString(ConfigManager::OWNER_EMAIL));
    }

    if (requestedInfo & REQUEST_MISC_SERVER_INFO) {
        output->addByte(0x12);
        output->addString(g_config.getString(ConfigManager::MOTD));
        output->addString(g_config.getString(ConfigManager::LOCATION));
        output->addString(g_config.getString(ConfigManager::URL));
        output->add<uint64_t>((OTSYS_TIME() - ProtocolStatus::start) / 1000);
    }

    if (requestedInfo & REQUEST_PLAYERS_INFO) {
        output->addByte(0x20);
        output->add<uint32_t>(g_game.getPlayersOnline());
        output->add<uint32_t>(g_config.getNumber(ConfigManager::MAX_PLAYERS));
        output->add<uint32_t>(g_game.getPlayersRecord());
    }

    if (requestedInfo & REQUEST_MAP_INFO) {
        output->addByte(0x30);
        output->addString(g_config.getString(ConfigManager::MAP_NAME));
        output->addString(g_config.getString(ConfigManager::MAP_AUTHOR));
        uint32_t mapWidth, mapHeight;
        g_game.getMapDimensions(mapWidth, mapHeight);
        output->add<uint16_t>(mapWidth);
        output->add<uint16_t>(mapHeight);
    }

    if (requestedInfo & REQUEST_EXT_PLAYERS_INFO) {
        output->addByte(0x21); // players info - online players list

        const auto& players = g_game.getPlayers();
        output->add<uint32_t>(players.size());
        for (const auto& it : players) {
            output->addString(it.second->getName());
            output->add<uint32_t>(it.second->getLevel());
        }
    }

    if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) {
        output->addByte(0x22); // players info - online status info of a player
        if (g_game.getPlayerByName(characterName) != nullptr) {
            output->addByte(0x01);
        } else {
            output->addByte(0x00);
        }
    }

    if (requestedInfo & REQUEST_SERVER_SOFTWARE_INFO) {
        output->addByte(0x23); // server software info
        output->addString(STATUS_SERVER_NAME);
        output->addString(STATUS_SERVER_VERSION);
        output->addString(CLIENT_VERSION_STR);
    }
    send(output);
    disconnect();
}
 
Solution
no way to send request for xml? What if the server is using old sources (eg. yurots or otserv) and can only answer to that old style request?
 
no way to send request for xml? What if the server is using old sources (eg. yurots or otserv) and can only answer to that old style request?
Then you need completely new Protocol class for the client (source), at least that's my guess based on how data is received by the client. At some point I got server to send XML data (checked by printing in server source) but client couldn't receive it.
 
I see. Sending this to localhost, got me xml answer, but my client could read only 2 bytes.
Code:
    msg:addU8(0xff)
    msg:addU8(0xff)
    msg:addU8(0x69)
    msg:addU8(0x6e)
    msg:addU8(0x66)
    msg:addU8(0x6f)

Could websockets in Kondra's client solve that problem?
 
I see. Sending this to localhost, got me xml answer, but my client could read only 2 bytes.
Code:
    msg:addU8(0xff)
    msg:addU8(0xff)
    msg:addU8(0x69)
    msg:addU8(0x6e)
    msg:addU8(0x66)
    msg:addU8(0x6f)

Could websockets in Kondra's client solve that?
Yeah, you can use HTTP request and get data from PHP script.
 
I see. I will look for solutions in cpp instead, using some TCP libraries. Thank you for your help.
 
I don't get it. It's being sent through boost asio async_write, but when I use boost asio async_read, I get broken results.
testRequest()
login
onConnect
sendStatusPacket
sent
onRecv
"forgotten" author="Komic" width="2048" height="2048"/><motd>Welcome to The Forgotten Server!</motd></tsqp>"3" spawn="1"/><map name=@B@A@B@A0AA@B@B0AA0AB@A@A@B@B@A@B@A0A
testRequest()
login
onConnect
sendStatusPacket
sent
onRecv
"forgotten" author="Komic" width="2048" height="2048"/><motd>Welcome to The Forgotten Server!</motd></tsqp>"3" spawn="1"/><map name=",["Yourroney"]urrney"]urr",["Yourr
testRequest()
login
onConnect
sendStatusPacket
sent
onRecv
"forgotten" author="Komic" width="2048" height="2048"/><motd>Welcome to The Forgotten Server!</motd></tsqp>"3" spawn="1"/><map name=gyX0x
testRequest()
login
onConnect
sendStatusPacket
sent
onRecv
"forgotten" author="Komic" width="2048" height="2048"/><motd>Welcome to The Forgotten Server!</motd></tsqp>"3" spawn="1"/><map name=] = "",["Two-Factor Authentification"] = "",["Type"] = "",["Unjustified Points"] = "",["Weight"] = "",["Will boost your walk
testRequest()
login
onConnect
sendStatusPacket
sent
onRecv
"forgotten" author="Komic" width="2048" height="2048"/><motd>Welcome to The Forgotten Server!</motd></tsqp>"3" spawn="1"/><map name=
testRequest()
login
onConnect
sendStatusPacket
sent
onRecv
"forgotten" author="Komic" width="2048" height="2048"/><motd>Welcome to The Forgotten Server!</motd></tsqp>"3" spawn="1"/><map name=ime"]kullme"]kull",["%sllf["%sll
testRequest()
login
onConnect
sendStatusPacket
sent
onRecv
"forgotten" author="Komic" width="2048" height="2048"/><motd>Welcome to The Forgotten Server!</motd></tsqp>"3" spawn="1"/><map name=tement"] = "",["Stay logged during session"] = "",["This offer is 25%% above the average market price"] = "",["This offer is 25
testRequest()
login
onConnect
sendStatusPacket
sent
onRecv
"forgotten" author="Komic" width="2048" height="2048"/><motd>Welcome to The Forgotten Server!</motd></tsqp>"3" spawn="1"/><map name=8""orgotten8""n8""pawn="1"/><mapame=tement"
testRequest()
login
onConnect
sendStatusPacket
sent
onRecv
"forgotten" author="Komic" width="2048" height="2048"/><motd>Welcome to The Forgotten Server!</motd></tsqp>"3" spawn="1"/><map name=tement"] = "",["Stay logged during session"] = "",["This offer is 25%% above the average market price"] = "",["This offer is 25

sending packet:
Code:
    local msg = OutputMessage.create()

    msg:addU8(0xff)
    msg:addU8(0xff)
    msg:addU8(0x69)
    msg:addU8(0x6e)
    msg:addU8(0x66)
    msg:addU8(0x6f)

    self:send(msg)
    pinfo("sent")
-- if I dont loop, I get connection lost error
-- if I loop, I get broken results
    for i = 1, 100 do
        self:recv(128)
    end

my protocol class modifications:
protocol.h:
Code:
virtual void recv(uint32_t amount = 2);

protocol.cpp:
Code:
void Protocol::recv(uint32_t amount)
{
    m_inputMessage->reset();

    // first update message header size
    int headerSize = 2; // 2 bytes for message size
    if(m_checksumEnabled)
        headerSize += 4; // 4 bytes for checksum
    if(m_xteaEncryptionEnabled)
        headerSize += 2; // 2 bytes for XTEA encrypted message size
    m_inputMessage->setHeaderSize(headerSize);

    // read the first 2 bytes which contain the message size
    if(m_connection)
        m_connection->read(amount, std::bind(&Protocol::internalRecvHeader, asProtocol(), std::placeholders::_1,  std::placeholders::_2));
}

How do I stabilize the reading?

Edit: nevermind, most servers answer to thing I marked as a solution anyway. Working on entergame redesign if anyone's curious.
 
Last edited:
Back
Top