• 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!
  • New resources must be posted under Resources tab. A discussion thread will be created automatically, you can't open threads manually anymore.

TFS 1.1 and Tibia 10.76

jo3bingham

Excellent OT User
Joined
Mar 3, 2008
Messages
1,103
Solutions
14
Reaction score
772
GitHub
jo3bingham
First of all, if this is the wrong board, feel free to move this thread accordingly.

Since I, and the rest of the OX team, have no plans of returning to the Tibia community, or releasing any of our work, I decided to contribute somewhat to the Open Tibia community by giving you a full breakdown of Tibia's update to 10.76 since TFS is not currently 100%.

Login
-protocollogin.cpp
--ProtocolLogin::onRecvFirstMessage()
The last 128 bytes of this NetworkMessage needs to be RSA decrypted to get the authenticator token, and the stayLoggedInForSession value. This should be done after getting the accountName and password.
Code:
std::string accountName = msg.GetString();
std::string password = msg.GetString();

msg.setPosition(msg.getLength() - 128);
if (!RSA_decrypt(msg)) {
    getConnection()->closeConnection();
    return;
}

std::string authenticatorToken = msg.GetString();
bool stayLoggedInForSession = msg.GetByte() == 0x01;
--ProtocolLogin::getCharacterList()
This is where I generated an authenticator token and made sure the one read from the NetworkMessage matched it. If no match, send authenticator token failed to the client and close the connection.
Code:
output->AddByte(0x0D);
output->AddByte(0x00); //this has to be sent, and, from my tests, is always 0
OutputMessagePool::getInstance()->send(output);
getConnection()->closeConnection();
return;
If there was a match then you need to tell the client.
Code:
output->AddByte(0x0C);
output->AddByte(0x00); //this has to be sent, and, from my test, is always 0
This should be done before adding session key, character list, premium, etc.

Now, personally, I don't like that TFS creates the session key out of the account name and password. Like real Tibia, TFS should generate a 30-character, alphanumeric string and use it as the session key. The whole point of the session key is to remove the account name and password from being stored in the client like before. But it's up to you guys as to what you want to do. I generated a random string, but if account name and password is fine with you then so be it.

Game
-protocolgame.cpp
--Parse Methods
0x1F = PerformanceMetrics
0x77 = EquipObject
0x9B = GuildMessage
0x9C = EditGuildMessage
0xF2 = RuleViolationReport

--PerformanceMetrics
I never used this information, but it could be handy to someone.
Code:
uint16_t objectCounterMinimum = msg.get<uint16_t>();
uint16_t objectCounterMaximum = msg.get<uint16_t>();
uint16_t objectCounterAverage = msg.get<uint16_t>();
uint16_t fpsCounterMinimum = msg.get<uint16_t>();
uint16_t fpsCounterMaximum = msg.get<uint16_t>();
uint16_t fpsCounterAverage = msg.get<uint16_t>();
uint16_t fpsLimit = msg.get<uint16_t>();

--EquipObject
Code:
uint16_t objectId = msg.get<uint16_t>();
uint8_t data = msg.get<uint8_t>();
data seems to always return 0, from my tests, so I'm not really sure what it's purpose is. This packet is for the new auto-equip CipSoft added to Tibia in the 10.76 update. objectId is the id of the item to equip, however, it is the client id and not the server id. So, for example, gold coin would be objectId of 3031, even though it's server id is 2048 (I believe). Now, it is up to you to write the code in the server to equip/unequip the item in game.cpp. I used a combination of Game::playerMoveItem() and Game::internalMoveItem() + player->sendInventoryItem() depending on whether the container the item was located in was closed or not, or if it was already equipped. Real Tibia works by first checking if the item is already equipped. If so, it places it in the first open slot in your backpack, no matter where that is. If not, it looks through your backpack, finds the first occurrence, then moves it to the corresponding inventory slot. If it's a stackable item (arrows, small stones, etc.) and you have 100 in your inventory slot, or no more in your backpack, it moves it to your backpack, otherwise, it moves them from your backpack until you have 100 in your inventory slot. It took me quite a bit of coding and improvements to get it 100% like real Tibia, so don't beat yourself up if it takes some time.

--GuildMessage
This packet has no extra data. Basically, it just tells the server that the player wants to edit the guild message of the day in the guild channel.

--EditGuildMessage
Code:
const std::string text = msg.GetString();
This packet contains the text that the player has changed the guild message of the day to.

--RuleViolationReport
Code:
uint8_t reportType = msg.GetByte();
uint8_t reason = msg.GetByte();
std::string playerName = msg.GetString();
std::string comment = msg.GetString();
std::string translation =  "";
uint32_t statementId = 0;
if (reportType == REPORT_NAME) {
    translation = msg.GetString();
} else if (reportType == REPORT_STATEMENT) {
    translation = msg.GetString();
    statementId = msg.get<uint32_t>();
}
In enums.h place:
Code:
enum ReportType : uint8_t {
    REPORT_NAME = 0,
    REPORT_STATEMENT = 1,
    REPORT_BOT = 2
};
CipSoft uses the same value for multiple reasons making it pretty much useless. For example, when reporting a name, reasons offensive, sexual, and one or two others shared the same reason value. The rest of the information can be useful, though.

--Send Methods
0x9C = Blessings
0x9D = SwitchPreset
0xB7 = UnjustifiedPoints
0xB8 = PvpSituations
0xAE = EditGuildMessage
0xF5 = PlayerObjects
0x9F = BasicData

--Blessings
I never used this packet in our server, or dug in to see what it's purpose was, but it passes a single uint16_t value.

--SwitchPreset
This is solely used in Dawnport in real Tibia when you switch professions (knight, paladin, druid, and sorcerer). It passes a single uint8_t value corresponding to the profession. I believe it's only used in the flash client.

--UnjustifiedPoints
This is for the Unjustified Points window in the client that shows your progress and skull. It passes seven uint8_t values as follows:
Code:
progressDay
killsRemainingDay
progressWeek
killsRemainingWeek
progressMonth
killsRemainingMonth
skullDuration
Check out the Unjustified Points window in the client to get a better understanding of each value.

--PvpSituations
I never used this packet in our server, or dug in to see what it's purpose was, but it passes a single uint8_t value.

--EditGuildMessage
This is what the server responds with when it receives the GuildMessage packet I mentioned above. It passes a string value containing the player's guild's current message of the day.

--PlayerObjects
This packet is the only way auto-equip will work. It contains the id and total count/charge of every single item the player has on them, inventory and backpack. It has to be sent to the client any time the player's inventory/backpack is changed in any way. If you put something in your backpack, send this packet. If you remove something from your backpack, send this packet. If you use a sudden death rune and it loses a charge, send this packet. You get the idea. The client stores this information, and, when you press an auto-equip hotkey, if it doesn't have that item stored it will not send the EquipObject packet to the server. I wrote my own function in game.cpp to iterate through the player cylinder and return a std::map<uint16_t, uint16_t> where first is item id and second is total count. When I say total count, I mean total count. If your player is carrying 12 stacks of 100 gold coins, and one stack of 35 gold coins, the total count would be 1235. Note that item ids have to be client ids, not server ids. So, like I said earlier, gold coin would be 3031 and not 2048.
Code:
std::map<uint16_t, uint16_t> items = g_game.getItemsFromCylinder(player);
msg.Add<uint16_t>(items.size() + 11);
//for whatever reason, and I don't know why, real Tibia always sends 11 1-count objects at the beginning.
for (uint16_t i = 1; i <= 11; i++) {
    msg.Add<uint16_t>(i);
    msg.AddByte(0); //always 0
    msg.Add<uint16_t>(1); // always 1
}
for (const auto& it : items) {
    msg.Add<uint16_t>(it.first);
    msg.AddByte(0); //always 0
    msg.Add<uint16_t>(it.second);
}

--BasicData
Currently, TFS sends a zero-value uint16_t at the end of this packet. However, this is actually the number of spells the player knows followed by the ids of these spells. This is important if you want to get spell/rune hotkeys working in the flash client. And since most servers don't make players purchase/learn spells, and it doesn't matter which known spells are sent to the client, you can change msg.add<uint16_t>(0x00) to:
Code:
msg.Add<uint16_t>(0xFF);
for (uint8_t i = 0; i < 0xFF; i++) {
    msg.AddByte(i);
}
 
Last edited:
Guild Channel
If you want the functionality of real Tibia's new guild channel, which is how the GuildMessage and EditGuildMessage packets are used, then you will need to change a few things. First, add the guild message class to const.h: MESSAGE_GUILD = 33. Also, in const.h, guild channels range from 10000 19999.
Code:
#define CHANNEL_GUILD_FIRST 10000
#define CHANNEL_GUILD_LAST 19999
To give each guild their own, unique guild channel id, I just added their guild id to CHANNEL_GUILD_FIRST. In protocolgame.cpp, you'll either need to modify ProtocolGame::sendTextMessage() to account for channel ids, or add a new function like I did, to send messages to the guild channel.
Code:
void ProtocolGame::sendTextMessage(MessageClasses mclass, uint16_t channelId, const std::string message)
{
    NetworkMessage msg;
    msg.AddByte(0xB4);
    msg.AddByte(mclass);
    msg.Add<uint16_t>(channelId);
    msg.AddString(message);
    writeToOutputBuffer(msg);
}
If a player is in a guild, the guild channel is automatically opened upon login. Also, players other than guild members can be invited to the channel. So, guild channels now include participants and invited players. Only leaders and vice-leaders can invite/kick participants, and edit the guild message. That should be enough information for anyone to fully implement real Tibia's guild channel if they so wish to, but, of course, you can continue to use TFS' current guild channel system.

Miscellaneous
That should cover the majority of the things TFS is missing. Others, such as guild banks, should be easy to implement without my help. Party and private channels now have their own range just like guild channels, but you can continue to use TFS' current system for both.
Code:
#define CHANNEL_PRIVATE_FIRST 10
#define CHANNEL_PRIVATE_LAST 9999
#define CHANNEL_PARTY_FIRST 20000
#define CHANNEL_PARTY_LAST 65533
Loot messages have their own message class (TFS doesn't use it); MESSAGE_LOOT = 31. I recommend TFS be updated to use this when sending loot messages.

If you have any questions, feel free to ask and I will try my best to answer them. If I made any errors, please point them out and I will fix them.
 
Thanks for this man will help a lot! Its a shame though that you guys wont contribute because of the amount of leaches in the community.. Completely understandable though. I would most likely be the same as well lol.
 
Thanks for this man will help a lot! Its a shame though that you guys wont contribute because of the amount of leaches in the community.. Completely understandable though. I would most likely be the same as well lol.
That and how much time we put in to it. 3 months straight, some weeks each of us putting in 80+ hours along with our full-time jobs. Too much to just give away. But there's enough information here for anyone to use.
 
That and how much time we put in to it. 3 months straight, some weeks each of us putting in 80+ hours along with our full-time jobs. Too much to just give away. But there's enough information here for anyone to use.

yeah i know man. I always said you guys should charge for your service. Just my thoughts. The hard part is when you use the information and it doesn't work haha
 
Thanks. Most of thing that you posted here i was working atm hahaha
Oh, and i was using the ollydbg in cip client i was really not understanding why the server sends PlayerIventory/PlayerObjects with first 11 items with id = index ,subtype = 0 and count = 1. Its send that premium purse idk.
 
Last edited:
Thanks. Most of thing that you posted here i was working atm hahaha
Oh, and i was using the ollydbg in cip client i was really not understanding why the server sends PlayerIventory/PlayerObjects with first 11 items with id = index ,subtype = 0 and count = 1. Its send that premium purse idk.
Yeah, I don't fully understand the purpose. I'm sure they have a good reason for it.
Good job Jo3. This makes me want to write up the web portion of the authenticator and session system.
By all means, go for it.
@Syntax what programming language has been used for that authenticator? + session
Web portion was written in Java. The engine I just used Google's open-source authenticator code which is in C.
 
I don't have a global server but is very good to know these informations, thanks a lot :D
 
Just wanted to say thank you @Jo3Bingham, because of yours and @Yamaken's tips I was able to make this for my own server :)

Tho I only used internalMoveItem, is there a problem that you faced when using only it that I should be aware of?
 
Just wanted to say thank you @Jo3Bingham, because of yours and @Yamaken's tips I was able to make this for my own server :)

Tho I only used internalMoveItem, is there a problem that you faced when using only it that I should be aware of?
Nope, I didn't have any problems using internalMoveItem. Only changes I made to it was adding an exhaust because as it is now, people can equip/unequip instantaneously.
 
Nope, I didn't have any problems using internalMoveItem. Only changes I made to it was adding an exhaust because as it is now, people can equip/unequip instantaneously.
I thought so and added an exhaust too. I was thinking of starting on the saving of hotkeys binding, any tips for that too?

Edit: a word
 
Last edited:
Good job man. I am gonna to implement the frags feature.
 
Ugh, all the work me and my server partner put down for the flash client and protocol updates down the drain :c

nice share though
 
Back
Top