diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3150690..268b7f9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -10,6 +10,7 @@ list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(cotire)
add_compile_options(-Wall -pipe -fvisibility=hidden)
+add_definitions(-DBOOST_ALL_NO_LIB)
if (CMAKE_COMPILER_IS_GNUCXX)
add_compile_options(-fno-strict-aliasing)
diff --git a/data/XML/outfits.xml b/data/XML/outfits.xml
new file mode 100644
index 0000000..fca7ddc
--- /dev/null
+++ b/data/XML/outfits.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 76c0cad..5beaa34 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -41,6 +41,7 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp
${CMAKE_CURRENT_LIST_DIR}/npc.cpp
${CMAKE_CURRENT_LIST_DIR}/otserv.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/outfit.cpp
${CMAKE_CURRENT_LIST_DIR}/outputmessage.cpp
${CMAKE_CURRENT_LIST_DIR}/party.cpp
${CMAKE_CURRENT_LIST_DIR}/player.cpp
@@ -66,5 +67,6 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/vocation.cpp
${CMAKE_CURRENT_LIST_DIR}/waitlist.cpp
${CMAKE_CURRENT_LIST_DIR}/wildcardtree.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/xtea.cpp
)
diff --git a/src/actions.cpp b/src/actions.cpp
index 439748d..c23df0d 100644
--- a/src/actions.cpp
+++ b/src/actions.cpp
@@ -224,7 +224,7 @@ Action* Actions::getAction(const Item* item)
return g_spells->getRuneSpell(item->getID());
}
-ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item)
+ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey)
{
if (Door* door = item->getDoor()) {
if (!door->canUse(player)) {
@@ -235,13 +235,17 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_
Action* action = getAction(item);
if (action) {
if (action->isScripted()) {
- if (action->executeUse(player, item, pos, nullptr, pos)) {
+ if (action->executeUse(player, item, pos, nullptr, pos, isHotkey)) {
return RETURNVALUE_NOERROR;
}
if (item->isRemoved()) {
return RETURNVALUE_CANNOTUSETHISOBJECT;
}
+ } else if (action->function) {
+ if (action->function(player, item, pos, nullptr, pos, isHotkey)) {
+ return RETURNVALUE_NOERROR;
+ }
}
}
@@ -273,6 +277,11 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_
openContainer = container;
}
+ uint32_t corpseOwner = container->getCorpseOwner();
+ if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) {
+ return RETURNVALUE_YOUARENOTTHEOWNER;
+ }
+
//open/close container
int32_t oldContainerId = player->getContainerID(openContainer);
if (oldContainerId != -1) {
@@ -309,12 +318,16 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_
return RETURNVALUE_CANNOTUSETHISOBJECT;
}
-bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item)
+bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey)
{
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL));
player->stopWalk();
- ReturnValue ret = internalUseItem(player, pos, index, item);
+ if (isHotkey) {
+ showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), -1));
+ }
+
+ ReturnValue ret = internalUseItem(player, pos, index, item, isHotkey);
if (ret != RETURNVALUE_NOERROR) {
player->sendCancelMessage(ret);
return false;
@@ -323,7 +336,7 @@ bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item*
}
bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& toPos,
- uint8_t toStackPos, Item* item, Creature* creature/* = nullptr*/)
+ uint8_t toStackPos, Item* item, bool isHotkey, Creature* creature/* = nullptr*/)
{
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL));
player->stopWalk();
@@ -340,7 +353,11 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position&
return false;
}
- if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos)) {
+ if (isHotkey) {
+ showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), -1));
+ }
+
+ if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos, isHotkey)) {
if (!action->hasOwnErrorHandler()) {
player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
}
@@ -349,8 +366,25 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position&
return true;
}
+void Actions::showUseHotkeyMessage(Player* player, const Item* item, uint32_t count)
+{
+ std::ostringstream ss;
+
+ const ItemType& it = Item::items[item->getID()];
+ if (!it.showCount) {
+ ss << "Using one of " << item->getName() << "...";
+ }
+ else if (count == 1) {
+ ss << "Using the last " << item->getName() << "...";
+ }
+ else {
+ ss << "Using one of " << count << ' ' << item->getPluralName() << "...";
+ }
+ player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
+}
+
Action::Action(LuaScriptInterface* interface) :
- Event(interface), allowFarUse(false), checkFloor(true), checkLineOfSight(true) {}
+ Event(interface), function(nullptr), allowFarUse(false), checkFloor(true), checkLineOfSight(true) {}
Action::Action(const Action* copy) :
Event(copy), allowFarUse(copy->allowFarUse), checkFloor(copy->checkFloor), checkLineOfSight(copy->checkLineOfSight) {}
@@ -359,17 +393,17 @@ bool Action::configureEvent(const pugi::xml_node& node)
{
pugi::xml_attribute allowFarUseAttr = node.attribute("allowfaruse");
if (allowFarUseAttr) {
- setAllowFarUse(allowFarUseAttr.as_bool());
+ allowFarUse = allowFarUseAttr.as_bool();
}
pugi::xml_attribute blockWallsAttr = node.attribute("blockwalls");
if (blockWallsAttr) {
- setCheckLineOfSight(blockWallsAttr.as_bool());
+ checkLineOfSight = blockWallsAttr.as_bool();
}
pugi::xml_attribute checkFloorAttr = node.attribute("checkfloor");
if (checkFloorAttr) {
- setCheckFloor(checkFloorAttr.as_bool());
+ checkFloor = checkFloorAttr.as_bool();
}
return true;
@@ -382,10 +416,11 @@ std::string Action::getScriptEventName() const
ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos)
{
- if (!getAllowFarUse()) {
+ if (!allowFarUse) {
return g_actions->canUse(player, toPos);
- } else {
- return g_actions->canUseFar(player, toPos, getCheckLineOfSight(), getCheckFloor());
+ }
+ else {
+ return g_actions->canUseFar(player, toPos, checkLineOfSight, checkFloor);
}
}
@@ -397,9 +432,9 @@ Thing* Action::getTarget(Player* player, Creature* targetCreature, const Positio
return g_game.internalGetThing(player, toPosition, toStackPos, 0, STACKPOS_USETARGET);
}
-bool Action::executeUse(Player* player, Item* item, const Position& fromPos, Thing* target, const Position& toPos)
+bool Action::executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey)
{
- //onUse(player, item, fromPosition, target, toPosition)
+ //onUse(player, item, fromPosition, target, toPosition, isHotkey)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - Action::executeUse] Call stack overflow" << std::endl;
return false;
@@ -416,10 +451,11 @@ bool Action::executeUse(Player* player, Item* item, const Position& fromPos, Thi
LuaScriptInterface::setMetatable(L, -1, "Player");
LuaScriptInterface::pushThing(L, item);
- LuaScriptInterface::pushPosition(L, fromPos);
+ LuaScriptInterface::pushPosition(L, fromPosition);
LuaScriptInterface::pushThing(L, target);
- LuaScriptInterface::pushPosition(L, toPos);
+ LuaScriptInterface::pushPosition(L, toPosition);
- return scriptInterface->callFunction(5);
-}
+ LuaScriptInterface::pushBoolean(L, isHotkey);
+ return scriptInterface->callFunction(6);
+}
\ No newline at end of file
diff --git a/src/actions.h b/src/actions.h
index f4ae6d9..6f03677 100644
--- a/src/actions.h
+++ b/src/actions.h
@@ -24,6 +24,10 @@
#include "enums.h"
#include "luascript.h"
+class Action;
+using Action_ptr = std::unique_ptr;
+using ActionFunction = std::function;
+
class Action : public Event
{
public:
@@ -34,7 +38,7 @@ class Action : public Event
//scripting
virtual bool executeUse(Player* player, Item* item, const Position& fromPosition,
- Thing* target, const Position& toPosition);
+ Thing* target, const Position& toPosition, bool isHotkey);
//
bool getAllowFarUse() const {
@@ -58,18 +62,44 @@ class Action : public Event
checkFloor = v;
}
+ std::vector getItemIdRange() {
+ return ids;
+ }
+ void addItemId(uint16_t id) {
+ ids.emplace_back(id);
+ }
+
+ std::vector getUniqueIdRange() {
+ return uids;
+ }
+ void addUniqueId(uint16_t id) {
+ uids.emplace_back(id);
+ }
+
+ std::vector getActionIdRange() {
+ return aids;
+ }
+ void addActionId(uint16_t id) {
+ aids.emplace_back(id);
+ }
+
virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos);
virtual bool hasOwnErrorHandler() {
return false;
}
virtual Thing* getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const;
- protected:
+ ActionFunction function;
+
+ private:
std::string getScriptEventName() const override;
- bool allowFarUse;
- bool checkFloor;
- bool checkLineOfSight;
+ bool allowFarUse = false;
+ bool checkFloor = true;
+ bool checkLineOfSight = true;
+ std::vector ids;
+ std::vector uids;
+ std::vector aids;
};
class Actions final : public BaseEvents
@@ -82,15 +112,16 @@ class Actions final : public BaseEvents
Actions(const Actions&) = delete;
Actions& operator=(const Actions&) = delete;
- bool useItem(Player* player, const Position& pos, uint8_t index, Item* item);
- bool useItemEx(Player* player, const Position& fromPos, const Position& toPos, uint8_t toStackPos, Item* item, Creature* creature = nullptr);
+ bool useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey);
+ bool useItemEx(Player* player, const Position& fromPos, const Position& toPos, uint8_t toStackPos, Item* item, bool isHotkey, Creature* creature = nullptr);
ReturnValue canUse(const Player* player, const Position& pos);
ReturnValue canUse(const Player* player, const Position& pos, const Item* item);
ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor);
- protected:
- ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item);
+ private:
+ ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey);
+ static void showUseHotkeyMessage(Player* player, const Item* item, uint32_t count);
void clear() final;
LuaScriptInterface& getScriptInterface() final;
diff --git a/src/configmanager.cpp b/src/configmanager.cpp
index 2993702..5b2b7a0 100644
--- a/src/configmanager.cpp
+++ b/src/configmanager.cpp
@@ -68,6 +68,7 @@ bool ConfigManager::load()
boolean[SHOW_MONSTER_LOOT] = getGlobalBoolean(L, "showMonsterLoot", true);
boolean[ALLOW_CHANGEOUTFIT] = getGlobalBoolean(L, "allowChangeOutfit", true);
boolean[ONE_PLAYER_ON_ACCOUNT] = getGlobalBoolean(L, "onePlayerOnlinePerAccount", true);
+ boolean[AIMBOT_HOTKEY_ENABLED] = getGlobalBoolean(L, "hotkeyAimbotEnabled", true);
boolean[REMOVE_RUNE_CHARGES] = getGlobalBoolean(L, "removeChargesFromRunes", true);
boolean[EXPERIENCE_FROM_PLAYERS] = getGlobalBoolean(L, "experienceByKillingPlayers", false);
boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false);
diff --git a/src/configmanager.h b/src/configmanager.h
index 6c14b68..1461eb0 100644
--- a/src/configmanager.h
+++ b/src/configmanager.h
@@ -29,6 +29,7 @@ class ConfigManager
SHOW_MONSTER_LOOT,
ALLOW_CHANGEOUTFIT,
ONE_PLAYER_ON_ACCOUNT,
+ AIMBOT_HOTKEY_ENABLED,
REMOVE_RUNE_CHARGES,
EXPERIENCE_FROM_PLAYERS,
FREE_PREMIUM,
diff --git a/src/enums.h b/src/enums.h
index 1ae3739..a403145 100644
--- a/src/enums.h
+++ b/src/enums.h
@@ -359,6 +359,7 @@ struct Outfit_t {
uint8_t lookBody = 0;
uint8_t lookLegs = 0;
uint8_t lookFeet = 0;
+ uint8_t lookAddons = 0;
};
struct LightInfo {
diff --git a/src/game.cpp b/src/game.cpp
index 1d587b6..905d7fe 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -1866,6 +1866,11 @@ void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t f
return;
}
+ bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0);
+ if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) {
+ return;
+ }
+
Thing* thing = internalGetThing(player, fromPos, fromStackPos, fromSpriteId, STACKPOS_USEITEM);
if (!thing) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
@@ -1934,7 +1939,7 @@ void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t f
player->resetIdleTime();
player->setNextActionTask(nullptr);
- g_actions->useItemEx(player, fromPos, toPos, toStackPos, item);
+ g_actions->useItemEx(player, fromPos, toPos, toStackPos, item, isHotkey);
}
void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos,
@@ -1945,6 +1950,11 @@ void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPo
return;
}
+ bool isHotkey = (pos.x == 0xFFFF && pos.y == 0 && pos.z == 0);
+ if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) {
+ return;
+ }
+
Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USEITEM);
if (!thing) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
@@ -1989,7 +1999,7 @@ void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPo
player->resetIdleTime();
player->setNextActionTask(nullptr);
- g_actions->useItem(player, pos, index, item);
+ g_actions->useItem(player, pos, index, item, isHotkey);
}
void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId)
@@ -2004,13 +2014,16 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin
return;
}
- if (creature->getPlayer()) {
- player->sendCancelMessage(RETURNVALUE_DIRECTPLAYERSHOOT);
+ if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) {
return;
}
- if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) {
- return;
+ bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0);
+ if (!g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) {
+ if (creature->getPlayer() || isHotkey) {
+ player->sendCancelMessage(RETURNVALUE_DIRECTPLAYERSHOOT);
+ return;
+ }
}
Thing* thing = internalGetThing(player, fromPos, fromStackPos, spriteId, STACKPOS_USEITEM);
@@ -2081,7 +2094,7 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin
player->resetIdleTime();
player->setNextActionTask(nullptr);
- g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->getThingIndex(creature), item, creature);
+ g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->getThingIndex(creature), item, isHotkey, creature);
}
void Game::playerCloseContainer(uint32_t playerId, uint8_t cid)
@@ -2874,7 +2887,7 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
return;
}
- if (player->canWear(outfit.lookType)) {
+ if (player->canWear(outfit.lookType, outfit.lookAddons)) {
player->defaultOutfit = outfit;
if (player->hasCondition(CONDITION_OUTFIT)) {
diff --git a/src/outfit.cpp b/src/outfit.cpp
new file mode 100644
index 0000000..9984e9a
--- /dev/null
+++ b/src/outfit.cpp
@@ -0,0 +1,77 @@
+/**
+ * The Forgotten Server - a free and open-source MMORPG server emulator
+ * Copyright (C) 2019 Mark Samman
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "otpch.h"
+
+#include "outfit.h"
+
+#include "pugicast.h"
+#include "tools.h"
+
+bool Outfits::loadFromXml()
+{
+ pugi::xml_document doc;
+ pugi::xml_parse_result result = doc.load_file("data/XML/outfits.xml");
+ if (!result) {
+ printXMLError("Error - Outfits::loadFromXml", "data/XML/outfits.xml", result);
+ return false;
+ }
+
+ for (auto outfitNode : doc.child("outfits").children()) {
+ pugi::xml_attribute attr;
+ if ((attr = outfitNode.attribute("enabled")) && !attr.as_bool()) {
+ continue;
+ }
+
+ if (!(attr = outfitNode.attribute("type"))) {
+ std::cout << "[Warning - Outfits::loadFromXml] Missing outfit type." << std::endl;
+ continue;
+ }
+
+ uint16_t type = pugi::cast(attr.value());
+ if (type > PLAYERSEX_LAST) {
+ std::cout << "[Warning - Outfits::loadFromXml] Invalid outfit type " << type << "." << std::endl;
+ continue;
+ }
+
+ pugi::xml_attribute lookTypeAttribute = outfitNode.attribute("looktype");
+ if (!lookTypeAttribute) {
+ std::cout << "[Warning - Outfits::loadFromXml] Missing looktype on outfit." << std::endl;
+ continue;
+ }
+
+ outfits[type].emplace_back(
+ outfitNode.attribute("name").as_string(),
+ pugi::cast(lookTypeAttribute.value()),
+ outfitNode.attribute("premium").as_bool(),
+ outfitNode.attribute("unlocked").as_bool(true)
+ );
+ }
+ return true;
+}
+
+const Outfit* Outfits::getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const
+{
+ for (const Outfit& outfit : outfits[sex]) {
+ if (outfit.lookType == lookType) {
+ return &outfit;
+ }
+ }
+ return nullptr;
+}
diff --git a/src/outfit.h b/src/outfit.h
new file mode 100644
index 0000000..50aefa6
--- /dev/null
+++ b/src/outfit.h
@@ -0,0 +1,63 @@
+/**
+ * The Forgotten Server - a free and open-source MMORPG server emulator
+ * Copyright (C) 2019 Mark Samman
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef FS_OUTFIT_H_C56E7A707E3F422C8C93D9BE09916AA3
+#define FS_OUTFIT_H_C56E7A707E3F422C8C93D9BE09916AA3
+
+#include "enums.h"
+
+struct Outfit {
+ Outfit(std::string name, uint16_t lookType, bool premium, bool unlocked) :
+ name(std::move(name)), lookType(lookType), premium(premium), unlocked(unlocked) {}
+
+ std::string name;
+ uint16_t lookType;
+ bool premium;
+ bool unlocked;
+};
+
+struct ProtocolOutfit {
+ ProtocolOutfit(const std::string& name, uint16_t lookType, uint8_t addons) :
+ name(name), lookType(lookType), addons(addons) {}
+
+ const std::string& name;
+ uint16_t lookType;
+ uint8_t addons;
+};
+
+class Outfits
+{
+ public:
+ static Outfits& getInstance() {
+ static Outfits instance;
+ return instance;
+ }
+
+ bool loadFromXml();
+
+ const Outfit* getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const;
+ const std::vector& getOutfits(PlayerSex_t sex) const {
+ return outfits[sex];
+ }
+
+ private:
+ std::vector outfits[PLAYERSEX_LAST + 1];
+};
+
+#endif
diff --git a/src/player.cpp b/src/player.cpp
index 57b15bf..7d17518 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -493,6 +493,11 @@ uint16_t Player::getContainerIndex(uint8_t cid) const
return it->second.index;
}
+bool Player::canOpenCorpse(uint32_t ownerId) const
+{
+ return getID() == ownerId || (party && party->canOpenCorpse(ownerId));
+}
+
uint16_t Player::getLookCorpse() const
{
if (sex == PLAYERSEX_FEMALE) {
@@ -3129,7 +3134,7 @@ bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/)
void Player::gainExperience(uint64_t gainExp, Creature* source)
{
- if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0) {
+ if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0 || staminaMinutes == 0) {
return;
}
@@ -3223,26 +3228,31 @@ void Player::changeSoul(int32_t soulChange)
sendStats();
}
-bool Player::canWear(uint32_t lookType) const
+bool Player::canWear(uint32_t lookType, uint8_t addons) const
{
if (group->access) {
return true;
}
- if (getSex() == PLAYERSEX_MALE) {
- if (lookType >= 132 && lookType <= 134 && isPremium()) {
- return true;
- } else if (lookType >= 128 && lookType <= 131) {
- return true;
- }
- } else if (getSex() == PLAYERSEX_FEMALE) {
- if (lookType >= 140 && lookType <= 142 && isPremium()) {
- return true;
- } else if (lookType >= 136 && lookType <= 139) {
- return true;
- }
+ const Outfit* outfit = Outfits::getInstance().getOutfitByLookType(sex, lookType);
+ if (!outfit) {
+ return false;
}
+ if (outfit->premium && !isPremium()) {
+ return false;
+ }
+
+ if (outfit->unlocked && addons == 0) {
+ return true;
+ }
+
+ for (const OutfitEntry& outfitEntry : outfits) {
+ if (outfitEntry.lookType != lookType) {
+ continue;
+ }
+ return (outfitEntry.addons & addons) == addons;
+ }
return false;
}
@@ -3263,6 +3273,77 @@ bool Player::canLogout()
return !isPzLocked() && !hasCondition(CONDITION_INFIGHT);
}
+void Player::genReservedStorageRange()
+{
+ //generate outfits range
+ uint32_t base_key = PSTRG_OUTFITS_RANGE_START;
+ for (const OutfitEntry& entry : outfits) {
+ storageMap[++base_key] = (entry.lookType << 16) | entry.addons;
+ }
+}
+
+void Player::addOutfit(uint16_t lookType, uint8_t addons)
+{
+ for (OutfitEntry& outfitEntry : outfits) {
+ if (outfitEntry.lookType == lookType) {
+ outfitEntry.addons |= addons;
+ return;
+ }
+ }
+ outfits.emplace_back(lookType, addons);
+}
+
+bool Player::removeOutfit(uint16_t lookType)
+{
+ for (auto it = outfits.begin(), end = outfits.end(); it != end; ++it) {
+ OutfitEntry& entry = *it;
+ if (entry.lookType == lookType) {
+ outfits.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Player::removeOutfitAddon(uint16_t lookType, uint8_t addons)
+{
+ for (OutfitEntry& outfitEntry : outfits) {
+ if (outfitEntry.lookType == lookType) {
+ outfitEntry.addons &= ~addons;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Player::getOutfitAddons(const Outfit& outfit, uint8_t& addons) const
+{
+ if (group->access) {
+ addons = 3;
+ return true;
+ }
+
+ if (outfit.premium && !isPremium()) {
+ return false;
+ }
+
+ for (const OutfitEntry& outfitEntry : outfits) {
+ if (outfitEntry.lookType != outfit.lookType) {
+ continue;
+ }
+
+ addons = outfitEntry.addons;
+ return true;
+ }
+
+ if (!outfit.unlocked) {
+ return false;
+ }
+
+ addons = 0;
+ return true;
+}
+
void Player::setSex(PlayerSex_t newSex)
{
sex = newSex;
diff --git a/src/player.h b/src/player.h
index 16e0f3b..743e2ac 100644
--- a/src/player.h
+++ b/src/player.h
@@ -23,6 +23,7 @@
#include "creature.h"
#include "container.h"
#include "cylinder.h"
+#include "outfit.h"
#include "enums.h"
#include "vocation.h"
#include "protocolgame.h"
@@ -77,9 +78,10 @@ struct OpenContainer {
};
struct OutfitEntry {
- constexpr OutfitEntry(uint16_t lookType) : lookType(lookType) {}
+ constexpr OutfitEntry(uint16_t lookType, uint8_t addons) : lookType(lookType), addons(addons) {}
uint16_t lookType;
+ uint8_t addons;
};
struct Skill {
@@ -148,6 +150,10 @@ class Player final : public Creature, public Cylinder
return ((50ULL * lv * lv * lv) - (150ULL * lv * lv) + (400ULL * lv)) / 3ULL;
}
+ uint16_t getStaminaMinutes() const {
+ return staminaMinutes;
+ }
+
uint64_t getBankBalance() const {
return bankBalance;
}
@@ -265,8 +271,11 @@ class Player final : public Creature, public Cylinder
int8_t getContainerID(const Container* container) const;
uint16_t getContainerIndex(uint8_t cid) const;
+ bool canOpenCorpse(uint32_t ownerId) const;
+
void addStorageValue(const uint32_t key, const int32_t value);
bool getStorageValue(const uint32_t key, int32_t& value) const;
+ void genReservedStorageRange();
void setGroup(Group* newGroup) {
group = newGroup;
@@ -556,7 +565,12 @@ class Player final : public Creature, public Cylinder
}
void checkSkullTicks();
- bool canWear(uint32_t lookType) const;
+ bool canWear(uint32_t lookType, uint8_t addons) const;
+ void addOutfit(uint16_t lookType, uint8_t addons);
+ bool removeOutfit(uint16_t lookType);
+ bool removeOutfitAddon(uint16_t lookType, uint8_t addons);
+ bool getOutfitAddons(const Outfit& outfit, uint8_t& addons) const;
+
bool canLogout();
@@ -1032,6 +1046,7 @@ class Player final : public Creature, public Cylinder
int32_t shieldBlockCount = 0;
int32_t idleTime = 0;
+ uint16_t staminaMinutes = 2520;
uint16_t maxWriteLen = 0;
uint8_t soul = 0;
diff --git a/src/protocol.cpp b/src/protocol.cpp
index 34f027c..00c7ebe 100644
--- a/src/protocol.cpp
+++ b/src/protocol.cpp
@@ -22,6 +22,7 @@
#include "protocol.h"
#include "outputmessage.h"
#include "rsa.h"
+#include "xtea.h"
extern RSA g_RSA;
@@ -32,7 +33,7 @@ void Protocol::onSendMessage(const OutputMessage_ptr& msg) const
if (encryptionEnabled) {
XTEA_encrypt(*msg);
- msg->addCryptoHeader();
+ msg->addCryptoHeader(checksumEnabled);
}
}
}
@@ -51,7 +52,8 @@ OutputMessage_ptr Protocol::getOutputBuffer(int32_t size)
//dispatcher thread
if (!outputBuffer) {
outputBuffer = OutputMessagePool::getOutputMessage();
- } else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) {
+ }
+ else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) {
send(outputBuffer);
outputBuffer = OutputMessagePool::getOutputMessage();
}
@@ -60,73 +62,27 @@ OutputMessage_ptr Protocol::getOutputBuffer(int32_t size)
void Protocol::XTEA_encrypt(OutputMessage& msg) const
{
- const uint32_t delta = 0x61C88647;
-
// The message must be a multiple of 8
- size_t paddingBytes = msg.getLength() % 8;
+ size_t paddingBytes = msg.getLength() % 8u;
if (paddingBytes != 0) {
msg.addPaddingBytes(8 - paddingBytes);
}
uint8_t* buffer = msg.getOutputBuffer();
- const size_t messageLength = msg.getLength();
- size_t readPos = 0;
- const uint32_t k[] = {key[0], key[1], key[2], key[3]};
- while (readPos < messageLength) {
- uint32_t v0;
- memcpy(&v0, buffer + readPos, 4);
- uint32_t v1;
- memcpy(&v1, buffer + readPos + 4, 4);
-
- uint32_t sum = 0;
-
- for (int32_t i = 32; --i >= 0;) {
- v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]);
- sum -= delta;
- v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[(sum >> 11) & 3]);
- }
-
- memcpy(buffer + readPos, &v0, 4);
- readPos += 4;
- memcpy(buffer + readPos, &v1, 4);
- readPos += 4;
- }
+ xtea::encrypt(buffer, msg.getLength(), key);
}
bool Protocol::XTEA_decrypt(NetworkMessage& msg) const
{
- if (((msg.getLength() - 2) % 8) != 0) {
+ if (((msg.getLength() - 6) & 7) != 0) {
return false;
}
- const uint32_t delta = 0x61C88647;
-
uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition();
- const size_t messageLength = (msg.getLength() - 6);
- size_t readPos = 0;
- const uint32_t k[] = {key[0], key[1], key[2], key[3]};
- while (readPos < messageLength) {
- uint32_t v0;
- memcpy(&v0, buffer + readPos, 4);
- uint32_t v1;
- memcpy(&v1, buffer + readPos + 4, 4);
+ xtea::decrypt(buffer, msg.getLength() - 6, key);
- uint32_t sum = 0xC6EF3720;
-
- for (int32_t i = 32; --i >= 0;) {
- v1 -= ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[(sum >> 11) & 3]);
- sum += delta;
- v0 -= ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]);
- }
-
- memcpy(buffer + readPos, &v0, 4);
- readPos += 4;
- memcpy(buffer + readPos, &v1, 4);
- readPos += 4;
- }
-
- int innerLength = msg.get();
- if (innerLength > msg.getLength() - 4) {
+ uint16_t innerLength = msg.get();
+ if (innerLength + 8 > msg.getLength()) {
return false;
}
@@ -151,4 +107,4 @@ uint32_t Protocol::getIP() const
}
return 0;
-}
+}
\ No newline at end of file
diff --git a/src/protocol.h b/src/protocol.h
index d2a46df..76bf5d1 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -21,77 +21,85 @@
#define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1
#include "connection.h"
+#include "xtea.h"
class Protocol : public std::enable_shared_from_this
{
- public:
- explicit Protocol(Connection_ptr connection) : connection(connection) {}
- virtual ~Protocol() = default;
+public:
+ explicit Protocol(Connection_ptr connection) : connection(connection) {}
+ virtual ~Protocol() = default;
- // non-copyable
- Protocol(const Protocol&) = delete;
- Protocol& operator=(const Protocol&) = delete;
+ // non-copyable
+ Protocol(const Protocol&) = delete;
+ Protocol& operator=(const Protocol&) = delete;
- virtual void parsePacket(NetworkMessage&) {}
+ virtual void parsePacket(NetworkMessage&) {}
- virtual void onSendMessage(const OutputMessage_ptr& msg) const;
- void onRecvMessage(NetworkMessage& msg);
- virtual void onRecvFirstMessage(NetworkMessage& msg) = 0;
- virtual void onConnect() {}
+ virtual void onSendMessage(const OutputMessage_ptr& msg) const;
+ void onRecvMessage(NetworkMessage& msg);
+ virtual void onRecvFirstMessage(NetworkMessage& msg) = 0;
+ virtual void onConnect() {}
- bool isConnectionExpired() const {
- return connection.expired();
+ bool isConnectionExpired() const {
+ return connection.expired();
+ }
+
+ Connection_ptr getConnection() const {
+ return connection.lock();
+ }
+
+ uint32_t getIP() const;
+
+ //Use this function for autosend messages only
+ OutputMessage_ptr getOutputBuffer(int32_t size);
+
+ OutputMessage_ptr& getCurrentBuffer() {
+ return outputBuffer;
+ }
+
+ void send(OutputMessage_ptr msg) const {
+ if (auto connection = getConnection()) {
+ connection->send(msg);
}
+ }
- Connection_ptr getConnection() const {
- return connection.lock();
+protected:
+ void disconnect() const {
+ if (auto connection = getConnection()) {
+ connection->close();
}
+ }
+ void enableXTEAEncryption() {
+ encryptionEnabled = true;
+ }
+ void setXTEAKey(xtea::key key) {
+ this->key = std::move(key);
+ }
+ void disableChecksum() {
+ checksumEnabled = false;
+ }
- uint32_t getIP() const;
+ static bool RSA_decrypt(NetworkMessage& msg);
- //Use this function for autosend messages only
- OutputMessage_ptr getOutputBuffer(int32_t size);
+ void setRawMessages(bool value) {
+ rawMessages = value;
+ }
- OutputMessage_ptr& getCurrentBuffer() {
- return outputBuffer;
- }
+ virtual void release() {}
- void send(OutputMessage_ptr msg) const {
- if (auto connection = getConnection()) {
- connection->send(msg);
- }
- }
+private:
+ void XTEA_encrypt(OutputMessage& msg) const;
+ bool XTEA_decrypt(NetworkMessage& msg) const;
- protected:
- void disconnect() const {
- if (auto connection = getConnection()) {
- connection->close();
- }
- }
- void enableXTEAEncryption() {
- encryptionEnabled = true;
- }
- void setXTEAKey(const uint32_t* key) {
- memcpy(this->key, key, sizeof(*key) * 4);
- }
+ friend class Connection;
- void XTEA_encrypt(OutputMessage& msg) const;
- bool XTEA_decrypt(NetworkMessage& msg) const;
- static bool RSA_decrypt(NetworkMessage& msg);
+ OutputMessage_ptr outputBuffer;
- void setRawMessages(bool value) {
- rawMessages = value;
- }
-
- virtual void release() {}
- friend class Connection;
-
- OutputMessage_ptr outputBuffer;
- private:
- const ConnectionWeak_ptr connection;
- uint32_t key[4] = {};
- bool encryptionEnabled = false;
- bool rawMessages = false;
+ const ConnectionWeak_ptr connection;
+ xtea::key key;
+ bool encryptionEnabled = false;
+ bool checksumEnabled = true;
+ bool rawMessages = false;
};
#endif
diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp
index 6142df6..f2536f0 100644
--- a/src/protocolgame.cpp
+++ b/src/protocolgame.cpp
@@ -242,18 +242,20 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
OperatingSystem_t operatingSystem = static_cast(msg.get());
version = msg.get();
+ msg.skipBytes(7); // U32 client version, U8 client type, U16 dat revision
+
if (!Protocol::RSA_decrypt(msg)) {
disconnect();
return;
}
- uint32_t key[4];
+ xtea::key key;
key[0] = msg.get();
key[1] = msg.get();
key[2] = msg.get();
key[3] = msg.get();
enableXTEAEncryption();
- setXTEAKey(key);
+ setXTEAKey(std::move(key));
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
NetworkMessage opcodeMessage;
@@ -315,12 +317,32 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem)));
}
-void ProtocolGame::sendUpdateRequest()
+void ProtocolGame::onConnect()
{
auto output = OutputMessagePool::getOutputMessage();
- output->addByte(0x11);
+ static std::random_device rd;
+ static std::ranlux24 generator(rd());
+ static std::uniform_int_distribution randNumber(0x00, 0xFF);
+
+ // Skip checksum
+ output->skipBytes(sizeof(uint32_t));
+
+ // Packet length & type
+ output->add(0x0006);
+ output->addByte(0x1F);
+
+ // Add timestamp & random number
+ challengeTimestamp = static_cast(time(nullptr));
+ output->add(challengeTimestamp);
+
+ challengeRandom = randNumber(generator);
+ output->addByte(challengeRandom);
+
+ // Go back and write checksum
+ output->skipBytes(-12);
+ output->add(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8));
+
send(output);
- disconnect();
}
void ProtocolGame::disconnectClient(const std::string& message) const
@@ -693,6 +715,7 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg)
newOutfit.lookBody = msg.getByte();
newOutfit.lookLegs = msg.getByte();
newOutfit.lookFeet = msg.getByte();
+ newOutfit.lookAddons = msg.getByte();
addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit);
}
@@ -1715,20 +1738,31 @@ void ProtocolGame::sendOutfitWindow()
Outfit_t currentOutfit = player->getDefaultOutfit();
AddOutfit(msg, currentOutfit);
- if (player->getSex() == PLAYERSEX_MALE) {
- msg.add(128);
- if (player->isPremium()) {
- msg.add(134);
- } else {
- msg.add(131);
- }
- } else {
- msg.add(136);
- if (player->isPremium()) {
- msg.add(142);
- } else {
- msg.add(139);
+ std::vector protocolOutfits;
+ if (player->isAccessPlayer()) {
+ static const std::string gamemasterOutfitName = "Gamemaster";
+ protocolOutfits.emplace_back(gamemasterOutfitName, 75, 0);
}
+
+ const auto& outfits = Outfits::getInstance().getOutfits(player->getSex());
+ protocolOutfits.reserve(outfits.size());
+ for (const Outfit& outfit : outfits) {
+ uint8_t addons;
+ if (!player->getOutfitAddons(outfit, addons)) {
+ continue;
+ }
+
+ protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons);
+ if (protocolOutfits.size() == 100) { // Game client doesn't allow more than 100 outfits
+ break;
+ }
+ }
+
+ msg.addByte(protocolOutfits.size());
+ for (const ProtocolOutfit& outfit : protocolOutfits) {
+ msg.add(outfit.lookType);
+ msg.addString(outfit.name);
+ msg.addByte(outfit.addons);
}
writeToOutputBuffer(msg);
@@ -1810,14 +1844,22 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
msg.add(std::min(player->getHealth(), std::numeric_limits::max()));
msg.add(std::min(player->getMaxHealth(), std::numeric_limits::max()));
- msg.add(static_cast(player->getFreeCapacity() / 100.));
- if (player->getExperience() >= std::numeric_limits::max()) {
- msg.add(0);
- } else {
- msg.add(static_cast(player->getExperience()));
- }
- msg.add(static_cast(player->getLevel()));
+ msg.add(player->getFreeCapacity());
+ msg.add(player->getCapacity());
+
+ msg.add(player->getExperience());
+
+ msg.add(player->getLevel());
+
msg.addByte(player->getLevelPercent());
+
+ msg.add(100); // base xp gain rate
+ msg.add(0); // xp voucher
+ msg.add(0); // low level bonus
+ msg.add(0); // xp boost
+ msg.add(100); // stamina multiplier (100 = x1.0)
+
+
msg.add(std::min(player->getMana(), std::numeric_limits::max()));
msg.add(std::min(player->getMaxMana(), std::numeric_limits::max()));
@@ -1825,6 +1867,10 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
msg.addByte(player->getMagicLevelPercent());
msg.addByte(player->getSoul());
+
+ msg.add(player->getStaminaMinutes());
+
+ msg.add(0); // xp boost time (seconds)
}
void ProtocolGame::AddPlayerSkills(NetworkMessage& msg)
@@ -1846,6 +1892,7 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit)
msg.addByte(outfit.lookBody);
msg.addByte(outfit.lookLegs);
msg.addByte(outfit.lookFeet);
+ msg.addByte(outfit.lookAddons);
} else {
msg.addItemId(outfit.lookTypeEx);
}
diff --git a/src/protocolgame.h b/src/protocolgame.h
index b5e7a8d..42c919d 100644
--- a/src/protocolgame.h
+++ b/src/protocolgame.h
@@ -50,8 +50,9 @@ class ProtocolGame final : public Protocol
{
public:
// static protocol information
- enum {server_sends_first = false};
- enum {protocol_identifier = 0x0A}; // Not required as we send first
+ enum { server_sends_first = true };
+ enum { protocol_identifier = 0 }; // Not required as we send first
+ enum { use_checksum = true };
static const char* protocol_name() {
return "gameworld protocol";
@@ -71,7 +72,6 @@ class ProtocolGame final : public Protocol
return std::static_pointer_cast(shared_from_this());
}
void connect(uint32_t playerId, OperatingSystem_t operatingSystem);
- void sendUpdateRequest();
void disconnectClient(const std::string& message) const;
void writeToOutputBuffer(const NetworkMessage& msg);
@@ -86,6 +86,7 @@ class ProtocolGame final : public Protocol
// we have all the parse methods
void parsePacket(NetworkMessage& msg) final;
void onRecvFirstMessage(NetworkMessage& msg) final;
+ void onConnect() override;
//Parse methods
void parseAutoWalk(NetworkMessage& msg);
@@ -262,8 +263,11 @@ class ProtocolGame final : public Protocol
Player* player = nullptr;
uint32_t eventConnect = 0;
+ uint32_t challengeTimestamp = 0;
uint16_t version = CLIENT_VERSION_MIN;
+ uint8_t challengeRandom = 0;
+
bool debugAssertSent = false;
bool acceptPackets = false;
};
diff --git a/src/protocollogin.cpp b/src/protocollogin.cpp
index be9a125..fb2602b 100644
--- a/src/protocollogin.cpp
+++ b/src/protocollogin.cpp
@@ -32,36 +32,39 @@
extern ConfigManager g_config;
extern Game g_game;
-void ProtocolLogin::sendUpdateRequest()
+void ProtocolLogin::disconnectClient(const std::string& message, uint16_t version)
{
auto output = OutputMessagePool::getOutputMessage();
- output->addByte(0x1E);
- send(output);
-
- disconnect();
-}
-
-void ProtocolLogin::disconnectClient(const std::string& message)
-{
- auto output = OutputMessagePool::getOutputMessage();
-
- output->addByte(0x0A);
+ output->addByte(version >= 1076 ? 0x0B : 0x0A);
output->addString(message);
send(output);
disconnect();
}
-void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string& password)
+void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string& password, const std::string& token, uint16_t version)
{
Account account;
if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) {
- disconnectClient("Accountnumber or password is not correct.");
+ disconnectClient("Accountnumber or password is not correct.", version);
return;
}
+ uint32_t ticks = time(nullptr) / AUTHENTICATOR_PERIOD;
+
auto output = OutputMessagePool::getOutputMessage();
+ if (!std::to_string(accountNumber).empty()) {
+ if (token.empty() || !(token == generateToken(std::to_string(accountNumber), ticks) || token == generateToken(std::to_string(accountNumber), ticks - 1) || token == generateToken(std::to_string(accountNumber), ticks + 1))) {
+ output->addByte(0x0D);
+ output->addByte(0);
+ send(output);
+ disconnect();
+ return;
+ }
+ output->addByte(0x0C);
+ output->addByte(0);
+ }
//Update premium days
Game::updatePremium(account);
@@ -76,23 +79,38 @@ void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string&
output->addString(ss.str());
}
+ //Add session key
+ output->addByte(0x28);
+ output->addString(std::to_string(accountNumber) + "\n" + password + "\n" + token + "\n" + std::to_string(ticks));
+
+
//Add char list
output->addByte(0x64);
+ output->addByte(1); // number of worlds
+
+ output->addByte(0); // world id
+ output->addString(g_config.getString(ConfigManager::SERVER_NAME));
+ output->addString(g_config.getString(ConfigManager::IP));
+ output->add(g_config.getNumber(ConfigManager::GAME_PORT));
+ output->addByte(0);
+
uint8_t size = std::min(std::numeric_limits::max(), account.characters.size());
output->addByte(size);
for (uint8_t i = 0; i < size; i++) {
+ output->addByte(0);
output->addString(account.characters[i]);
- output->addString(g_config.getString(ConfigManager::SERVER_NAME));
- output->add(inet_addr(g_config.getString(ConfigManager::IP).c_str()));
- output->add(g_config.getNumber(ConfigManager::GAME_PORT));
}
//Add premium days
+ output->addByte(0);
if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) {
- output->add(0xFFFF);
- } else {
- output->add(account.premiumDays);
+ output->addByte(1);
+ output->add(0);
+ }
+ else {
+ output->addByte(account.premiumDays > 0 ? 1 : 0);
+ output->add(time(nullptr) + (account.premiumDays * 86400));
}
send(output);
@@ -109,41 +127,55 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
msg.skipBytes(2); // client OS
- /*uint16_t version =*/ msg.get();
- msg.skipBytes(12);
+ uint16_t version = msg.get();
+ if (version >= 971) {
+ msg.skipBytes(17);
+ }
+ else {
+ msg.skipBytes(12);
+ }
/*
* Skipped bytes:
* 4 bytes: protocolVersion
* 12 bytes: dat, spr, pic signatures (4 bytes each)
+ * 1 byte: 0
*/
+ if (version <= 760) {
+ std::ostringstream ss;
+ ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
+ disconnectClient(ss.str(), version);
+ return;
+ }
+
if (!Protocol::RSA_decrypt(msg)) {
disconnect();
return;
}
- uint32_t key[4];
+ xtea::key key;
key[0] = msg.get();
key[1] = msg.get();
key[2] = msg.get();
key[3] = msg.get();
enableXTEAEncryption();
- setXTEAKey(key);
+ setXTEAKey(std::move(key));
- /*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) {
- //sendUpdateRequest();
- disconnectClient("Use Tibia 7.72 to login!");
+ if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) {
+ std::ostringstream ss;
+ ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
+ disconnectClient(ss.str(), version);
return;
- }*/
+ }
if (g_game.getGameState() == GAME_STATE_STARTUP) {
- disconnectClient("Gameworld is starting up. Please wait.");
+ disconnectClient("Gameworld is starting up. Please wait.", version);
return;
}
if (g_game.getGameState() == GAME_STATE_MAINTAIN) {
- disconnectClient("Gameworld is under maintenance.\nPlease re-connect in a while.");
+ disconnectClient("Gameworld is under maintenance.\nPlease re-connect in a while.", version);
return;
}
@@ -160,7 +192,7 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
std::ostringstream ss;
ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
- disconnectClient(ss.str());
+ disconnectClient(ss.str(), version);
return;
}
@@ -176,6 +208,15 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
return;
}
+ // read authenticator token and stay logged in flag from last 128 bytes
+ msg.skipBytes((msg.getLength() - 128) - msg.getBufferPosition());
+ if (!Protocol::RSA_decrypt(msg)) {
+ disconnectClient("Invalid authentification token.", version);
+ return;
+ }
+
+ std::string authToken = msg.getString();
+
auto thisPtr = std::static_pointer_cast(shared_from_this());
- g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password)));
+ g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password, version)));
}
diff --git a/src/protocollogin.h b/src/protocollogin.h
index 17004f6..4651b50 100644
--- a/src/protocollogin.h
+++ b/src/protocollogin.h
@@ -38,13 +38,12 @@ class ProtocolLogin : public Protocol
explicit ProtocolLogin(Connection_ptr connection) : Protocol(connection) {}
- void onRecvFirstMessage(NetworkMessage& msg);
+ void onRecvFirstMessage(NetworkMessage& msg) override;
- protected:
- void sendUpdateRequest();
- void disconnectClient(const std::string& message);
+ private:
+ void disconnectClient(const std::string& message, uint16_t version);
- void getCharacterList(uint32_t accountNumber, const std::string& password);
+ void getCharacterList(uint32_t accountNumber, const std::string& password, const std::string& token, uint16_t version);
};
#endif
diff --git a/src/rsa.cpp b/src/rsa.cpp
index affcad4..ba557ac 100644
--- a/src/rsa.cpp
+++ b/src/rsa.cpp
@@ -21,72 +21,60 @@
#include "rsa.h"
-RSA::RSA()
+#include
+#include
+
+#include
+#include
+
+static CryptoPP::AutoSeededRandomPool prng;
+
+void RSA::decrypt(char* msg) const
{
- mpz_init(n);
- mpz_init2(d, 1024);
+ CryptoPP::Integer m{ reinterpret_cast(msg), 128 };
+ auto c = pk.CalculateInverse(prng, m);
+ c.Encode(reinterpret_cast(msg), 128);
}
-RSA::~RSA()
+static const std::string header = "-----BEGIN RSA PRIVATE KEY-----";
+static const std::string footer = "-----END RSA PRIVATE KEY-----";
+
+void RSA::loadPEM(const std::string& filename)
{
- mpz_clear(n);
- mpz_clear(d);
-}
-
-void RSA::setKey(const char* pString, const char* qString)
-{
- mpz_t p, q, e;
- mpz_init2(p, 1024);
- mpz_init2(q, 1024);
- mpz_init(e);
-
- mpz_set_str(p, pString, 10);
- mpz_set_str(q, qString, 10);
-
- // e = 65537
- mpz_set_ui(e, 65537);
-
- // n = p * q
- mpz_mul(n, p, q);
-
- mpz_t p_1, q_1, pq_1;
- mpz_init2(p_1, 1024);
- mpz_init2(q_1, 1024);
- mpz_init2(pq_1, 1024);
-
- mpz_sub_ui(p_1, p, 1);
- mpz_sub_ui(q_1, q, 1);
-
- // pq_1 = (p -1)(q - 1)
- mpz_mul(pq_1, p_1, q_1);
-
- // d = e^-1 mod (p - 1)(q - 1)
- mpz_invert(d, e, pq_1);
-
- mpz_clear(p_1);
- mpz_clear(q_1);
- mpz_clear(pq_1);
-
- mpz_clear(p);
- mpz_clear(q);
- mpz_clear(e);
-}
-
-void RSA::decrypt(char* msg) const
-{
- mpz_t c, m;
- mpz_init2(c, 1024);
- mpz_init2(m, 1024);
-
- mpz_import(c, 128, 1, 1, 0, 0, msg);
-
- // m = c^d mod n
- mpz_powm(m, c, d, n);
-
- size_t count = (mpz_sizeinbase(m, 2) + 7) / 8;
- memset(msg, 0, 128 - count);
- mpz_export(msg + (128 - count), nullptr, 1, 1, 0, 0, m);
-
- mpz_clear(c);
- mpz_clear(m);
+ std::ifstream file{ filename };
+
+ if (!file.is_open()) {
+ throw std::runtime_error("Missing file " + filename + ".");
+ }
+
+ std::ostringstream oss;
+ for (std::string line; std::getline(file, line); oss << line);
+ std::string key = oss.str();
+
+ if (key.substr(0, header.size()) != header) {
+ throw std::runtime_error("Missing RSA private key header.");
+ }
+
+ if (key.substr(key.size() - footer.size(), footer.size()) != footer) {
+ throw std::runtime_error("Missing RSA private key footer.");
+ }
+
+ key = key.substr(header.size(), key.size() - footer.size());
+
+ CryptoPP::ByteQueue queue;
+ CryptoPP::Base64Decoder decoder;
+ decoder.Attach(new CryptoPP::Redirector(queue));
+ decoder.Put(reinterpret_cast(key.c_str()), key.size());
+ decoder.MessageEnd();
+
+ try {
+ pk.BERDecodePrivateKey(queue, false, queue.MaxRetrievable());
+
+ if (!pk.Validate(prng, 3)) {
+ throw std::runtime_error("RSA private key is not valid.");
+ }
+ }
+ catch (const CryptoPP::Exception& e) {
+ std::cout << e.what() << '\n';
+ }
}
diff --git a/src/rsa.h b/src/rsa.h
index e75335b..0783e7c 100644
--- a/src/rsa.h
+++ b/src/rsa.h
@@ -20,24 +20,24 @@
#ifndef FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91
#define FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91
-#include
+#include
+
+#include
class RSA
{
- public:
- RSA();
- ~RSA();
+public:
+ RSA() = default;
- // non-copyable
- RSA(const RSA&) = delete;
- RSA& operator=(const RSA&) = delete;
+ // non-copyable
+ RSA(const RSA&) = delete;
+ RSA& operator=(const RSA&) = delete;
- void setKey(const char* pString, const char* qString);
- void decrypt(char* msg) const;
+ void loadPEM(const std::string& filename);
+ void decrypt(char* msg) const;
- private:
- //use only GMP
- mpz_t n, d;
+private:
+ CryptoPP::RSA::PrivateKey pk;
};
-#endif
+#endif
\ No newline at end of file
diff --git a/src/server.cpp b/src/server.cpp
index 1923c74..1b09123 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -132,7 +132,7 @@ void ServicePort::onAccept(Connection_ptr connection, const boost::system::error
}
}
-Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const
+Protocol_ptr ServicePort::make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const
{
uint8_t protocolID = msg.getByte();
for (auto& service : services) {
@@ -140,7 +140,9 @@ Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_pt
continue;
}
- return service->make_protocol(connection);
+ if ((checksummed && service->is_checksummed()) || !service->is_checksummed()) {
+ return service->make_protocol(connection);
+ }
}
return nullptr;
}
diff --git a/src/server.h b/src/server.h
index f6ff611..02e5278 100644
--- a/src/server.h
+++ b/src/server.h
@@ -29,6 +29,7 @@ class ServiceBase
{
public:
virtual bool is_single_socket() const = 0;
+ virtual bool is_checksummed() const = 0;
virtual uint8_t get_protocol_identifier() const = 0;
virtual const char* get_protocol_name() const = 0;
@@ -39,17 +40,20 @@ template
class Service final : public ServiceBase
{
public:
- bool is_single_socket() const final {
+ bool is_single_socket() const override {
return ProtocolType::server_sends_first;
}
- uint8_t get_protocol_identifier() const final {
+ bool is_checksummed() const override {
+ return ProtocolType::use_checksum;
+ }
+ uint8_t get_protocol_identifier() const override {
return ProtocolType::protocol_identifier;
}
- const char* get_protocol_name() const final {
+ const char* get_protocol_name() const override {
return ProtocolType::protocol_name();
}
- Protocol_ptr make_protocol(const Connection_ptr& c) const final {
+ Protocol_ptr make_protocol(const Connection_ptr& c) const override {
return std::make_shared(c);
}
};
@@ -71,7 +75,7 @@ class ServicePort : public std::enable_shared_from_this
std::string get_protocol_names() const;
bool add_service(const Service_ptr& new_svc);
- Protocol_ptr make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const;
+ Protocol_ptr make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const;
void onStopServer();
void onAccept(Connection_ptr connection, const boost::system::error_code& error);
diff --git a/src/tools.cpp b/src/tools.cpp
index 14e0320..05be137 100644
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -272,6 +272,51 @@ std::string pluralizeString(std::string str)
return str2;
}
+std::string generateToken(const std::string& key, uint32_t ticks)
+{
+ // generate message from ticks
+ std::string message(8, 0);
+ for (uint8_t i = 8; --i; ticks >>= 8) {
+ message[i] = static_cast(ticks & 0xFF);
+ }
+
+ // hmac key pad generation
+ std::string iKeyPad(64, 0x36), oKeyPad(64, 0x5C);
+ for (uint8_t i = 0; i < key.length(); ++i) {
+ iKeyPad[i] ^= key[i];
+ oKeyPad[i] ^= key[i];
+ }
+
+ oKeyPad.reserve(84);
+
+ // hmac concat inner pad with message
+ iKeyPad.append(message);
+
+ // hmac first pass
+ message.assign(transformToSHA1(iKeyPad));
+
+ // hmac concat outer pad with message, conversion from hex to int needed
+ for (uint8_t i = 0; i < message.length(); i += 2) {
+ oKeyPad.push_back(static_cast(std::strtoul(message.substr(i, 2).c_str(), nullptr, 16)));
+ }
+
+ // hmac second pass
+ message.assign(transformToSHA1(oKeyPad));
+
+ // calculate hmac offset
+ uint32_t offset = static_cast(std::strtoul(message.substr(39, 1).c_str(), nullptr, 16) & 0xF);
+
+ // get truncated hash
+ uint32_t truncHash = static_cast(std::strtoul(message.substr(2 * offset, 8).c_str(), nullptr, 16)) & 0x7FFFFFFF;
+ message.assign(std::to_string(truncHash));
+
+ // return only last AUTHENTICATOR_DIGITS (default 6) digits, also asserts exactly 6 digits
+ uint32_t hashLen = message.length();
+ message.assign(message.substr(hashLen - std::min(hashLen, AUTHENTICATOR_DIGITS)));
+ message.insert(0, AUTHENTICATOR_DIGITS - std::min(hashLen, AUTHENTICATOR_DIGITS), '0');
+ return message;
+}
+
void replaceString(std::string& str, const std::string& sought, const std::string& replacement)
{
size_t pos = 0;
@@ -790,6 +835,33 @@ std::string getSkillName(uint8_t skillid)
}
}
+uint32_t adlerChecksum(const uint8_t* data, size_t length)
+{
+ if (length > NETWORKMESSAGE_MAXSIZE) {
+ return 0;
+ }
+
+ const uint16_t adler = 65521;
+
+ uint32_t a = 1, b = 0;
+
+ while (length > 0) {
+ size_t tmp = length > 5552 ? 5552 : length;
+ length -= tmp;
+
+ do {
+ a += *data++;
+ b += a;
+ } while (--tmp);
+
+ a %= adler;
+ b %= adler;
+ }
+
+ return (b << 16) | a;
+}
+
+
std::string ucfirst(std::string str)
{
for (char& i : str) {
diff --git a/src/tools.h b/src/tools.h
index 1edc868..b615af3 100644
--- a/src/tools.h
+++ b/src/tools.h
@@ -30,6 +30,7 @@
void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result);
std::string transformToSHA1(const std::string& input);
+std::string generateToken(const std::string& key, uint32_t ticks);
uint8_t getLiquidColor(uint8_t type);
void extractArticleAndName(std::string& data, std::string& article, std::string& name);
@@ -83,6 +84,8 @@ std::string getCombatName(CombatType_t combatType);
std::string getSkillName(uint8_t skillid);
+uint32_t adlerChecksum(const uint8_t* data, size_t length);
+
std::string ucfirst(std::string str);
std::string ucwords(std::string str);
bool booleanString(const std::string& str);
diff --git a/src/xtea.cpp b/src/xtea.cpp
new file mode 100644
index 0000000..1144ba6
--- /dev/null
+++ b/src/xtea.cpp
@@ -0,0 +1,140 @@
+/**
+ * The Forgotten Server - a free and open-source MMORPG server emulator
+ * Copyright (C) 2019 Mark Samman
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "otpch.h"
+
+#include "xtea.h"
+
+#include
+#include
+
+namespace xtea {
+
+namespace {
+
+constexpr uint32_t delta = 0x9E3779B9;
+
+template
+void XTEA_encrypt(uint8_t data[BLOCK_SIZE * 8], const key& k)
+{
+ alignas(16) uint32_t left[BLOCK_SIZE], right[BLOCK_SIZE];
+ for (auto i = 0u, j = 0u; i < BLOCK_SIZE; i += 1u, j += 8u) {
+ left[i] = data[j] | data[j+1] << 8u | data[j+2] << 16u | data[j+3] << 24u;
+ right[i] = data[j+4] | data[j+5] << 8u | data[j+6] << 16u | data[j+7] << 24u;
+ }
+
+ uint32_t sum = 0u;
+ for (auto i = 0u; i < 32; ++i) {
+ for (auto j = 0u; j < BLOCK_SIZE; ++j) {
+ left[j] += (((right[j] << 4) ^ (right[j] >> 5)) + right[j]) ^ (sum + k[sum & 3]);
+ }
+ sum += delta;
+ for (auto j = 0u; j < BLOCK_SIZE; ++j) {
+ right[j] += (((left[j] << 4) ^ (left[j] >> 5)) + left[j]) ^ (sum + k[(sum >> 11) & 3]);
+ }
+ }
+
+ for (auto i = 0u, j = 0u; i < BLOCK_SIZE; i += 1u, j += 8u) {
+ data[j] = static_cast(left[i]);
+ data[j+1] = static_cast(left[i] >> 8u);
+ data[j+2] = static_cast(left[i] >> 16u);
+ data[j+3] = static_cast(left[i] >> 24u);
+ data[j+4] = static_cast(right[i]);
+ data[j+5] = static_cast(right[i] >> 8u);
+ data[j+6] = static_cast(right[i] >> 16u);
+ data[j+7] = static_cast(right[i] >> 24u);
+ }
+}
+
+template
+void XTEA_decrypt(uint8_t data[BLOCK_SIZE * 8], const key& k)
+{
+ alignas(16) uint32_t left[BLOCK_SIZE], right[BLOCK_SIZE];
+ for (auto i = 0u, j = 0u; i < BLOCK_SIZE; i += 1u, j += 8u) {
+ left[i] = data[j] | data[j+1] << 8u | data[j+2] << 16u | data[j+3] << 24u;
+ right[i] = data[j+4] | data[j+5] << 8u | data[j+6] << 16u | data[j+7] << 24u;
+ }
+
+ uint32_t sum = delta << 5;
+ for (auto i = 0u; i < 32; ++i) {
+ for (auto j = 0u; j < BLOCK_SIZE; ++j) {
+ right[j] -= (((left[j] << 4) ^ (left[j] >> 5)) + left[j]) ^ (sum + k[(sum >> 11) & 3]);
+ }
+ sum -= delta;
+ for (auto j = 0u; j < BLOCK_SIZE; ++j) {
+ left[j] -= (((right[j] << 4) ^ (right[j] >> 5)) + right[j]) ^ (sum + k[(sum) & 3]);
+ }
+ }
+
+ for (auto i = 0u, j = 0u; i < BLOCK_SIZE; i += 1u, j += 8u) {
+ data[j] = static_cast(left[i]);
+ data[j+1] = static_cast(left[i] >> 8u);
+ data[j+2] = static_cast(left[i] >> 16u);
+ data[j+3] = static_cast(left[i] >> 24u);
+ data[j+4] = static_cast(right[i]);
+ data[j+5] = static_cast(right[i] >> 8u);
+ data[j+6] = static_cast(right[i] >> 16u);
+ data[j+7] = static_cast(right[i] >> 24u);
+ }
+}
+
+constexpr auto InitialBlockSize =
+#if defined(__AVX512F__)
+ 128u;
+#elif defined(__AVX__)
+ 32u;
+#elif defined(__SSE__) || defined(__ARM_FEATURE_SIMD32)
+ 8u;
+#elif defined(__x86_64__)
+ 2u;
+#else
+ 1u;
+#endif
+
+template
+struct XTEA {
+ static constexpr auto step = BlockSize * 8u;
+
+ void operator()(uint8_t* input, size_t length, const key& k) const {
+ const auto blocks = (length & ~(step - 1));
+ for (auto i = 0u; i < blocks; i += step) {
+ if (Encrypt) {
+ XTEA_encrypt(input + i, k);
+ } else {
+ XTEA_decrypt(input + i, k);
+ }
+ }
+ input += blocks;
+ length -= blocks;
+
+ if (BlockSize != 1) {
+ XTEA()(input, length, k);
+ }
+ }
+};
+
+constexpr auto encrypt_v = XTEA();
+constexpr auto decrypt_v = XTEA();
+
+} // anonymous namespace
+
+void encrypt(uint8_t* data, size_t length, const key& k) { encrypt_v(data, length, k); }
+void decrypt(uint8_t* data, size_t length, const key& k) { decrypt_v(data, length, k); }
+
+} // namespace xtea
diff --git a/src/xtea.h b/src/xtea.h
new file mode 100644
index 0000000..6f8f555
--- /dev/null
+++ b/src/xtea.h
@@ -0,0 +1,32 @@
+/**
+ * The Forgotten Server - a free and open-source MMORPG server emulator
+ * Copyright (C) 2019 Mark Samman
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TFS_XTEA_H
+#define TFS_XTEA_H
+
+namespace xtea {
+
+using key = std::array;
+
+void encrypt(uint8_t* data, size_t length, const key& k);
+void decrypt(uint8_t* data, size_t length, const key& k);
+
+} // namespace xtea
+
+#endif // TFS_XTEA_H
diff --git a/vc14/theforgottenserver.vcxproj b/vc14/theforgottenserver.vcxproj
index 8d24fba..afb71d6 100644
--- a/vc14/theforgottenserver.vcxproj
+++ b/vc14/theforgottenserver.vcxproj
@@ -190,6 +190,7 @@
otpch.h
+
@@ -215,6 +216,7 @@
+
@@ -263,6 +265,7 @@
+
@@ -291,6 +294,7 @@
+