• 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!

Help understanding parsing server/client

Togu

Advanced OT User
Joined
Jun 22, 2018
Messages
308
Solutions
1
Reaction score
178
Location
Brazil
I'm studying TFS 1.3 and OTClient sources to improve my project. And now I need to parse a new information (attack speed) to OTClient.

So, till now, what I understood is that the server can send and receive informations or commands (packets) to/from the client.
And client can send and receive informations or commands (packets) to/from the server too.

1) So, in server, we have in protocolgame.cpp:
Code:
void ProtocolGame::parsePacket(NetworkMessage& msg)
{
    if (!acceptPackets || g_game.getGameState() == GAME_STATE_SHUTDOWN || msg.getLength() <= 0) {
        return;
    }

    uint8_t recvbyte = msg.getByte();

    if (!player) {
        if (recvbyte == 0x0F) {
            disconnect();
        }

        return;
    }

    //a dead player can not performs actions
    if (player->isRemoved() || player->getHealth() <= 0) {
        if (recvbyte == 0x0F) {
            disconnect();
            return;
        }

        if (recvbyte != 0x14) {
            return;
        }
    }

    switch (recvbyte) {
        case 0x14: g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::logout, getThis(), true, false))); break;
        case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break;
        case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break;
        case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode
        case 0x64: parseAutoWalk(msg); break;
        case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break;
        case 0x66: addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST); break;
        case 0x67: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTH); break;
        case 0x68: addGameTask(&Game::playerMove, player->getID(), DIRECTION_WEST); break;
        case 0x69: addGameTask(&Game::playerStopAutoWalk, player->getID()); break;
        case 0x6A: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHEAST); break;
        case 0x6B: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHEAST); break;
        case 0x6C: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHWEST); break;
        case 0x6D: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHWEST); break;
        case 0x6F: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_NORTH); break;
        case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break;
        case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break;
        case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break;
        case 0x77: parseEquipObject(msg); break;
        case 0x78: parseThrow(msg); break;
        case 0x79: parseLookInShop(msg); break;
        case 0x7A: parsePlayerPurchase(msg); break;
        case 0x7B: parsePlayerSale(msg); break;
        case 0x7C: addGameTask(&Game::playerCloseShop, player->getID()); break;
        case 0x7D: parseRequestTrade(msg); break;
        case 0x7E: parseLookInTrade(msg); break;
        case 0x7F: addGameTask(&Game::playerAcceptTrade, player->getID()); break;
        case 0x80: addGameTask(&Game::playerCloseTrade, player->getID()); break;
        case 0x82: parseUseItem(msg); break;
        case 0x83: parseUseItemEx(msg); break;
        case 0x84: parseUseWithCreature(msg); break;
        case 0x85: parseRotateItem(msg); break;
        case 0x87: parseCloseContainer(msg); break;
        case 0x88: parseUpArrowContainer(msg); break;
        case 0x89: parseTextWindow(msg); break;
        case 0x8A: parseHouseWindow(msg); break;
        case 0x8C: parseLookAt(msg); break;
        case 0x8D: parseLookInBattleList(msg); break;
        case 0x8E: /* join aggression */ break;
        case 0x96: parseSay(msg); break;
        case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break;
        case 0x98: parseOpenChannel(msg); break;
        case 0x99: parseCloseChannel(msg); break;
        case 0x9A: parseOpenPrivateChannel(msg); break;
        case 0x9E: addGameTask(&Game::playerCloseNpcChannel, player->getID()); break;
        case 0xA0: parseFightModes(msg); break;
        case 0xA1: parseAttack(msg); break;
        case 0xA2: parseFollow(msg); break;
        case 0xA3: parseInviteToParty(msg); break;
        case 0xA4: parseJoinParty(msg); break;
        case 0xA5: parseRevokePartyInvite(msg); break;
        case 0xA6: parsePassPartyLeadership(msg); break;
        case 0xA7: addGameTask(&Game::playerLeaveParty, player->getID()); break;
        case 0xA8: parseEnableSharedPartyExperience(msg); break;
        case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break;
        case 0xAB: parseChannelInvite(msg); break;
        case 0xAC: parseChannelExclude(msg); break;
        case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break;
        case 0xC9: /* update tile */ break;
        case 0xCA: parseUpdateContainer(msg); break;
        case 0xCB: parseBrowseField(msg); break;
        case 0xCC: parseSeekInContainer(msg); break;
        case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break;
        case 0xD3: parseSetOutfit(msg); break;
        case 0xD4: parseToggleMount(msg); break;
        case 0xDC: parseAddVip(msg); break;
        case 0xDD: parseRemoveVip(msg); break;
        case 0xDE: parseEditVip(msg); break;
        case 0xE6: parseBugReport(msg); break;
        case 0xE7: /* thank you */ break;
        case 0xE8: parseDebugAssert(msg); break;
        case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break;
        case 0xF1: parseQuestLine(msg); break;
        case 0xF2: parseRuleViolationReport(msg); break;
        case 0xF3: /* get object info */ break;
        case 0xF4: parseMarketLeave(); break;
        case 0xF5: parseMarketBrowse(msg); break;
        case 0xF6: parseMarketCreateOffer(msg); break;
        case 0xF7: parseMarketCancelOffer(msg); break;
        case 0xF8: parseMarketAcceptOffer(msg); break;
        case 0xF9: parseModalWindowAnswer(msg); break;

        default:
            // std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast<uint16_t>(recvbyte) << std::dec << "!" << std::endl;
            break;
    }

    if (msg.isOverrun()) {
        disconnect();
    }
}

This function receives commands/information from connected clients, am I right?

2) We also have in protocolgame.cpp the send methods, and one of them is "sendStats":
Code:
void ProtocolGame::sendStats()
{
    NetworkMessage msg;
    AddPlayerStats(msg);
    writeToOutputBuffer(msg);
}

void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
{
    msg.addByte(0xA0);

    msg.add<uint16_t>(std::min<int32_t>(player->getHealth(), std::numeric_limits<uint16_t>::max()));
    msg.add<uint16_t>(std::min<int32_t>(player->getMaxHealth(), std::numeric_limits<uint16_t>::max()));

    msg.add<uint32_t>(player->getFreeCapacity());
    msg.add<uint32_t>(player->getCapacity());

    msg.add<uint64_t>(player->getExperience());

    msg.add<uint16_t>(player->getLevel());
    msg.addByte(player->getLevelPercent());

    msg.add<uint16_t>(100); // base xp gain rate
    msg.add<uint16_t>(0); // xp voucher
    msg.add<uint16_t>(0); // low level bonus
    msg.add<uint16_t>(0); // xp boost
    msg.add<uint16_t>(100); // stamina multiplier (100 = x1.0)

    msg.add<uint16_t>(std::min<int32_t>(player->getMana(), std::numeric_limits<uint16_t>::max()));
    msg.add<uint16_t>(std::min<int32_t>(player->getMaxMana(), std::numeric_limits<uint16_t>::max()));

    msg.addByte(std::min<uint32_t>(player->getMagicLevel(), std::numeric_limits<uint8_t>::max()));
    msg.addByte(std::min<uint32_t>(player->getBaseMagicLevel(), std::numeric_limits<uint8_t>::max()));
    msg.addByte(player->getMagicLevelPercent());

    msg.addByte(player->getSoul());

    msg.add<uint16_t>(player->getStaminaMinutes());

    msg.add<uint16_t>(player->getBaseSpeed() / 2);

    Condition* condition = player->getCondition(CONDITION_REGENERATION);
    msg.add<uint16_t>(condition ? condition->getTicks() / 1000 : 0x00);

    msg.add<uint16_t>(player->getOfflineTrainingTime() / 60 / 1000);

    msg.add<uint16_t>(0); // xp boost time (seconds)
    msg.addByte(0); // enables exp boost in the store
}

void ProtocolGame::writeToOutputBuffer(const NetworkMessage& msg)
{
    auto out = getOutputBuffer(msg.getLength());
    out->append(msg);
}

And on client we have in protocolgameparse.cpp:
Code:
void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
{
    double health;
    double maxHealth;

    if(g_game.getFeature(Otc::GameDoubleHealth)) {
        health = msg->getU32();
        maxHealth = msg->getU32();
    } else {
        health = msg->getU16();
        maxHealth = msg->getU16();
    }

    double freeCapacity;
    if(g_game.getFeature(Otc::GameDoubleFreeCapacity))
        freeCapacity = msg->getU32() / 100.0;
    else
        freeCapacity = msg->getU16() / 100.0;

    double totalCapacity = 0;
    if(g_game.getFeature(Otc::GameTotalCapacity))
        totalCapacity = msg->getU32() / 100.0;

    double experience;
    if(g_game.getFeature(Otc::GameDoubleExperience))
        experience = msg->getU64();
    else
        experience = msg->getU32();

    double level = msg->getU16();
    double levelPercent = msg->getU8();

    if(g_game.getFeature(Otc::GameExperienceBonus)) {
        if(g_game.getClientVersion() <= 1096) {
            double experienceBonus = msg->getDouble();
        } else {
            int baseXpGain = msg->getU16();
            int voucherAddend = msg->getU16();
            int grindingAddend = msg->getU16();
            int storeBoostAddend = msg->getU16();
            int huntingBoostFactor = msg->getU16();
        }
    }

    double mana;
    double maxMana;

    if(g_game.getFeature(Otc::GameDoubleHealth)) {
        mana = msg->getU32();
        maxMana = msg->getU32();
    } else {
        mana = msg->getU16();
        maxMana = msg->getU16();
    }

    double magicLevel = msg->getU8();

    double baseMagicLevel;
    if(g_game.getFeature(Otc::GameSkillsBase))
        baseMagicLevel = msg->getU8();
    else
        baseMagicLevel = magicLevel;

    double magicLevelPercent = msg->getU8();
    double soul = msg->getU8();
    double stamina = 0;
    if(g_game.getFeature(Otc::GamePlayerStamina))
        stamina = msg->getU16();

    double baseSpeed = 0;
    if(g_game.getFeature(Otc::GameSkillsBase))
        baseSpeed = msg->getU16();

    double regeneration = 0;
    if(g_game.getFeature(Otc::GamePlayerRegenerationTime))
        regeneration = msg->getU16();

    double training = 0;
    if(g_game.getFeature(Otc::GameOfflineTrainingTime)) {
        training = msg->getU16();
        if(g_game.getClientVersion() >= 1097) {
            int remainingStoreXpBoostSeconds = msg->getU16();
            bool canBuyMoreStoreXpBoosts = msg->getU8();
        }
    }

    m_localPlayer->setHealth(health, maxHealth);
    m_localPlayer->setFreeCapacity(freeCapacity);
    m_localPlayer->setTotalCapacity(totalCapacity);
    m_localPlayer->setExperience(experience);
    m_localPlayer->setLevel(level, levelPercent);
    m_localPlayer->setMana(mana, maxMana);
    m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent);
    m_localPlayer->setBaseMagicLevel(baseMagicLevel);
    m_localPlayer->setStamina(stamina);
    m_localPlayer->setSoul(soul);
    m_localPlayer->setBaseSpeed(baseSpeed);
    m_localPlayer->setRegenerationTime(regeneration);
    m_localPlayer->setOfflineTrainingTime(training);
}

What I want to do is replace:
Code:
msg.add<uint16_t>(player->getOfflineTrainingTime() / 60 / 1000);

For something like:
Code:
msg.add<uint16_t>(player->getAttackSpeed());

But I don't know what exactly this will do. Didn't understand how things work. How the client identifies the exactly byte sent? How do I know which byte is available to use? Why sometimes is used msg.add and other times msg.addByte ?

I'm a student, so I want theorical answers, I'll be glad if anybody can help me with that.

Edit:
I was following this post [Help] how to parse the information sent form the client (https://otland.net/threads/help-how-to-parse-the-information-sent-form-the-client.252669/) to understand things.
 
If you send packet to client it has to be able to receive it (you need to code that). Why once msg.add and once msg.addByte? Because msg.add is used to send numbers (or strings) larger than byte, you can specify the type of int you want to send (msg.add<uint16_t>(number)). If you want to parse that in client, you do msg->getU16()

uint32_t is U32 and so on.
 
If you send packet to client it has to be able to receive it (you need to code that). Why once msg.add and once msg.addByte? Because msg.add is used to send numbers (or strings) larger than byte, you can specify the type of int you want to send (msg.add<uint16_t>(number)). If you want to parse that in client, you do msg->getU16()

uint32_t is U32 and so on.
Thanks, it helped a bit.

Other question:
Code:
msg.addByte(0xA0);
Whats the meaning of this line? Why some functions add a random byte like this in the beginning and others dont?
 
Thanks, it helped a bit.

Other question:
Code:
msg.addByte(0xA0);
Whats the meaning of this line? Why some functions add a random byte like this in the beginning and others dont?
This is called packet header, its used to recognize what this packet is. You can find them in protocolgame in tfs, every packet has header which is something else. Your client (or server) uses that to recognize which one it is and to read correctly the sent message.
 
And how can a packet be sent wrong? How this impact client performance?
I've seen somewhere that Gesior corrected many packets that were being send wrong to client to correct animations.
 
Last edited:
Would it be possible to increase the speed at which this package is processed? example, what wpe does, but being configurable at source.
 
Back
Top