first changes

This commit is contained in:
ErikasKontenis 2019-09-29 21:56:37 +03:00
parent 9299222bd1
commit ad4cf36193
28 changed files with 1042 additions and 310 deletions

View File

@ -10,6 +10,7 @@ list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(cotire) include(cotire)
add_compile_options(-Wall -pipe -fvisibility=hidden) add_compile_options(-Wall -pipe -fvisibility=hidden)
add_definitions(-DBOOST_ALL_NO_LIB)
if (CMAKE_COMPILER_IS_GNUCXX) if (CMAKE_COMPILER_IS_GNUCXX)
add_compile_options(-fno-strict-aliasing) add_compile_options(-fno-strict-aliasing)

110
data/XML/outfits.xml Normal file
View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<outfits>
<!-- Female outfits -->
<outfit type="0" looktype="136" name="Citizen" premium="no" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="137" name="Hunter" premium="no" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="138" name="Mage" premium="no" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="139" name="Knight" premium="no" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="140" name="Noblewoman" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="141" name="Summoner" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="142" name="Warrior" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="147" name="Barbarian" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="148" name="Druid" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="149" name="Wizard" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="150" name="Oriental" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="0" looktype="155" name="Pirate" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="156" name="Assassin" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="157" name="Beggar" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="158" name="Shaman" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="270" name="Jester" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="288" name="Demon Hunter" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="324" name="Yalaharian" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="329" name="Newly Wed" premium="no" unlocked="no" enabled="yes" />
<outfit type="0" looktype="336" name="Warmaster" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="366" name="Wayfarer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="431" name="Afflicted" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="433" name="Elementalist" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="464" name="Deepling" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="466" name="Insectoid" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="471" name="Entrepreneur" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="513" name="Crystal Warlord" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="514" name="Soil Guardian" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="542" name="Demon Outfit" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="575" name="Cave Explorer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="578" name="Dream Warden" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="618" name="Glooth Engineer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="620" name="Jersey" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="632" name="Champion" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="635" name="Conjurer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="636" name="Beastmaster" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="664" name="Chaos Acolyte" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="666" name="Death Herald" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="683" name="Ranger" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="694" name="Ceremonial Garb" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="696" name="Puppeteer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="698" name="Spirit Caller" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="724" name="Evoker" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="732" name="Seaweaver" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="745" name="Recruiter" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="749" name="Sea Dog" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="759" name="Royal Pumpkin" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="845" name="Rift Warrior" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="852" name="Winter Warden" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="874" name="Philosopher" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="885" name="Arena Champion" premium="yes" unlocked="no" enabled="yes" />
<outfit type="0" looktype="900" name="Lupine Warden" premium="yes" unlocked="no" enabled="yes" />
<!-- Male outfits -->
<outfit type="1" looktype="128" name="Citizen" premium="no" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="129" name="Hunter" premium="no" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="130" name="Mage" premium="no" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="131" name="Knight" premium="no" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="132" name="Nobleman" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="133" name="Summoner" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="134" name="Warrior" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="143" name="Barbarian" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="144" name="Druid" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="145" name="Wizard" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="146" name="Oriental" premium="yes" unlocked="yes" enabled="yes" />
<outfit type="1" looktype="151" name="Pirate" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="152" name="Assassin" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="153" name="Beggar" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="154" name="Shaman" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="273" name="Jester" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="289" name="Demon Hunter" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="325" name="Yalaharian" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="328" name="Newly Wed" premium="no" unlocked="no" enabled="yes" />
<outfit type="1" looktype="335" name="Warmaster" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="367" name="Wayfarer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="430" name="Afflicted" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="432" name="Elementalist" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="463" name="Deepling" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="465" name="Insectoid" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="472" name="Entrepreneur" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="512" name="Crystal Warlord" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="516" name="Soil Guardian" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="541" name="Demon Outfit" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="574" name="Cave Explorer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="577" name="Dream Warden" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="610" name="Glooth Engineer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="619" name="Jersey" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="633" name="Champion" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="634" name="Conjurer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="637" name="Beastmaster" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="665" name="Chaos Acolyte" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="667" name="Death Herald" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="684" name="Ranger" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="695" name="Ceremonial Garb" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="697" name="Puppeteer" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="699" name="Spirit Caller" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="725" name="Evoker" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="733" name="Seaweaver" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="746" name="Recruiter" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="750" name="Sea Dog" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="760" name="Royal Pumpkin" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="846" name="Rift Warrior" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="853" name="Winter Warden" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="873" name="Philosopher" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="884" name="Arena Champion" premium="yes" unlocked="no" enabled="yes" />
<outfit type="1" looktype="899" name="Lupine Warden" premium="yes" unlocked="no" enabled="yes" />
</outfits>

View File

@ -41,6 +41,7 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp ${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp
${CMAKE_CURRENT_LIST_DIR}/npc.cpp ${CMAKE_CURRENT_LIST_DIR}/npc.cpp
${CMAKE_CURRENT_LIST_DIR}/otserv.cpp ${CMAKE_CURRENT_LIST_DIR}/otserv.cpp
${CMAKE_CURRENT_LIST_DIR}/outfit.cpp
${CMAKE_CURRENT_LIST_DIR}/outputmessage.cpp ${CMAKE_CURRENT_LIST_DIR}/outputmessage.cpp
${CMAKE_CURRENT_LIST_DIR}/party.cpp ${CMAKE_CURRENT_LIST_DIR}/party.cpp
${CMAKE_CURRENT_LIST_DIR}/player.cpp ${CMAKE_CURRENT_LIST_DIR}/player.cpp
@ -66,5 +67,6 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/vocation.cpp ${CMAKE_CURRENT_LIST_DIR}/vocation.cpp
${CMAKE_CURRENT_LIST_DIR}/waitlist.cpp ${CMAKE_CURRENT_LIST_DIR}/waitlist.cpp
${CMAKE_CURRENT_LIST_DIR}/wildcardtree.cpp ${CMAKE_CURRENT_LIST_DIR}/wildcardtree.cpp
${CMAKE_CURRENT_LIST_DIR}/xtea.cpp
) )

View File

@ -224,7 +224,7 @@ Action* Actions::getAction(const Item* item)
return g_spells->getRuneSpell(item->getID()); 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* door = item->getDoor()) {
if (!door->canUse(player)) { if (!door->canUse(player)) {
@ -235,13 +235,17 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_
Action* action = getAction(item); Action* action = getAction(item);
if (action) { if (action) {
if (action->isScripted()) { if (action->isScripted()) {
if (action->executeUse(player, item, pos, nullptr, pos)) { if (action->executeUse(player, item, pos, nullptr, pos, isHotkey)) {
return RETURNVALUE_NOERROR; return RETURNVALUE_NOERROR;
} }
if (item->isRemoved()) { if (item->isRemoved()) {
return RETURNVALUE_CANNOTUSETHISOBJECT; 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; openContainer = container;
} }
uint32_t corpseOwner = container->getCorpseOwner();
if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) {
return RETURNVALUE_YOUARENOTTHEOWNER;
}
//open/close container //open/close container
int32_t oldContainerId = player->getContainerID(openContainer); int32_t oldContainerId = player->getContainerID(openContainer);
if (oldContainerId != -1) { if (oldContainerId != -1) {
@ -309,12 +318,16 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_
return RETURNVALUE_CANNOTUSETHISOBJECT; 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->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL));
player->stopWalk(); 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) { if (ret != RETURNVALUE_NOERROR) {
player->sendCancelMessage(ret); player->sendCancelMessage(ret);
return false; 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, 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->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL));
player->stopWalk(); player->stopWalk();
@ -340,7 +353,11 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position&
return false; 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()) { if (!action->hasOwnErrorHandler()) {
player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
} }
@ -349,8 +366,25 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position&
return true; 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) : 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) : Action::Action(const Action* copy) :
Event(copy), allowFarUse(copy->allowFarUse), checkFloor(copy->checkFloor), checkLineOfSight(copy->checkLineOfSight) {} 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"); pugi::xml_attribute allowFarUseAttr = node.attribute("allowfaruse");
if (allowFarUseAttr) { if (allowFarUseAttr) {
setAllowFarUse(allowFarUseAttr.as_bool()); allowFarUse = allowFarUseAttr.as_bool();
} }
pugi::xml_attribute blockWallsAttr = node.attribute("blockwalls"); pugi::xml_attribute blockWallsAttr = node.attribute("blockwalls");
if (blockWallsAttr) { if (blockWallsAttr) {
setCheckLineOfSight(blockWallsAttr.as_bool()); checkLineOfSight = blockWallsAttr.as_bool();
} }
pugi::xml_attribute checkFloorAttr = node.attribute("checkfloor"); pugi::xml_attribute checkFloorAttr = node.attribute("checkfloor");
if (checkFloorAttr) { if (checkFloorAttr) {
setCheckFloor(checkFloorAttr.as_bool()); checkFloor = checkFloorAttr.as_bool();
} }
return true; return true;
@ -382,10 +416,11 @@ std::string Action::getScriptEventName() const
ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos) ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos)
{ {
if (!getAllowFarUse()) { if (!allowFarUse) {
return g_actions->canUse(player, toPos); 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); 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()) { if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - Action::executeUse] Call stack overflow" << std::endl; std::cout << "[Error - Action::executeUse] Call stack overflow" << std::endl;
return false; return false;
@ -416,10 +451,11 @@ bool Action::executeUse(Player* player, Item* item, const Position& fromPos, Thi
LuaScriptInterface::setMetatable(L, -1, "Player"); LuaScriptInterface::setMetatable(L, -1, "Player");
LuaScriptInterface::pushThing(L, item); LuaScriptInterface::pushThing(L, item);
LuaScriptInterface::pushPosition(L, fromPos); LuaScriptInterface::pushPosition(L, fromPosition);
LuaScriptInterface::pushThing(L, target); LuaScriptInterface::pushThing(L, target);
LuaScriptInterface::pushPosition(L, toPos); LuaScriptInterface::pushPosition(L, toPosition);
return scriptInterface->callFunction(5); LuaScriptInterface::pushBoolean(L, isHotkey);
return scriptInterface->callFunction(6);
} }

View File

@ -24,6 +24,10 @@
#include "enums.h" #include "enums.h"
#include "luascript.h" #include "luascript.h"
class Action;
using Action_ptr = std::unique_ptr<Action>;
using ActionFunction = std::function<bool(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey)>;
class Action : public Event class Action : public Event
{ {
public: public:
@ -34,7 +38,7 @@ class Action : public Event
//scripting //scripting
virtual bool executeUse(Player* player, Item* item, const Position& fromPosition, 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 { bool getAllowFarUse() const {
@ -58,18 +62,44 @@ class Action : public Event
checkFloor = v; checkFloor = v;
} }
std::vector<uint16_t> getItemIdRange() {
return ids;
}
void addItemId(uint16_t id) {
ids.emplace_back(id);
}
std::vector<uint16_t> getUniqueIdRange() {
return uids;
}
void addUniqueId(uint16_t id) {
uids.emplace_back(id);
}
std::vector<uint16_t> getActionIdRange() {
return aids;
}
void addActionId(uint16_t id) {
aids.emplace_back(id);
}
virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos); virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos);
virtual bool hasOwnErrorHandler() { virtual bool hasOwnErrorHandler() {
return false; return false;
} }
virtual Thing* getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const; virtual Thing* getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const;
protected: ActionFunction function;
private:
std::string getScriptEventName() const override; std::string getScriptEventName() const override;
bool allowFarUse; bool allowFarUse = false;
bool checkFloor; bool checkFloor = true;
bool checkLineOfSight; bool checkLineOfSight = true;
std::vector<uint16_t> ids;
std::vector<uint16_t> uids;
std::vector<uint16_t> aids;
}; };
class Actions final : public BaseEvents class Actions final : public BaseEvents
@ -82,15 +112,16 @@ class Actions final : public BaseEvents
Actions(const Actions&) = delete; Actions(const Actions&) = delete;
Actions& operator=(const Actions&) = delete; Actions& operator=(const Actions&) = delete;
bool useItem(Player* player, const Position& pos, uint8_t index, Item* item); 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, Creature* creature = nullptr); 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);
ReturnValue canUse(const Player* player, const Position& pos, const Item* item); ReturnValue canUse(const Player* player, const Position& pos, const Item* item);
ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor); ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor);
protected: private:
ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item); 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; void clear() final;
LuaScriptInterface& getScriptInterface() final; LuaScriptInterface& getScriptInterface() final;

View File

@ -68,6 +68,7 @@ bool ConfigManager::load()
boolean[SHOW_MONSTER_LOOT] = getGlobalBoolean(L, "showMonsterLoot", true); boolean[SHOW_MONSTER_LOOT] = getGlobalBoolean(L, "showMonsterLoot", true);
boolean[ALLOW_CHANGEOUTFIT] = getGlobalBoolean(L, "allowChangeOutfit", true); boolean[ALLOW_CHANGEOUTFIT] = getGlobalBoolean(L, "allowChangeOutfit", true);
boolean[ONE_PLAYER_ON_ACCOUNT] = getGlobalBoolean(L, "onePlayerOnlinePerAccount", 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[REMOVE_RUNE_CHARGES] = getGlobalBoolean(L, "removeChargesFromRunes", true);
boolean[EXPERIENCE_FROM_PLAYERS] = getGlobalBoolean(L, "experienceByKillingPlayers", false); boolean[EXPERIENCE_FROM_PLAYERS] = getGlobalBoolean(L, "experienceByKillingPlayers", false);
boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false); boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false);

View File

@ -29,6 +29,7 @@ class ConfigManager
SHOW_MONSTER_LOOT, SHOW_MONSTER_LOOT,
ALLOW_CHANGEOUTFIT, ALLOW_CHANGEOUTFIT,
ONE_PLAYER_ON_ACCOUNT, ONE_PLAYER_ON_ACCOUNT,
AIMBOT_HOTKEY_ENABLED,
REMOVE_RUNE_CHARGES, REMOVE_RUNE_CHARGES,
EXPERIENCE_FROM_PLAYERS, EXPERIENCE_FROM_PLAYERS,
FREE_PREMIUM, FREE_PREMIUM,

View File

@ -359,6 +359,7 @@ struct Outfit_t {
uint8_t lookBody = 0; uint8_t lookBody = 0;
uint8_t lookLegs = 0; uint8_t lookLegs = 0;
uint8_t lookFeet = 0; uint8_t lookFeet = 0;
uint8_t lookAddons = 0;
}; };
struct LightInfo { struct LightInfo {

View File

@ -1866,6 +1866,11 @@ void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t f
return; 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); Thing* thing = internalGetThing(player, fromPos, fromStackPos, fromSpriteId, STACKPOS_USEITEM);
if (!thing) { if (!thing) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
@ -1934,7 +1939,7 @@ void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t f
player->resetIdleTime(); player->resetIdleTime();
player->setNextActionTask(nullptr); 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, 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; 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); Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USEITEM);
if (!thing) { if (!thing) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
@ -1989,7 +1999,7 @@ void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPo
player->resetIdleTime(); player->resetIdleTime();
player->setNextActionTask(nullptr); 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) void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId)
@ -2004,14 +2014,17 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin
return; return;
} }
if (creature->getPlayer()) { if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) {
player->sendCancelMessage(RETURNVALUE_DIRECTPLAYERSHOOT);
return; return;
} }
if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) { 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; return;
} }
}
Thing* thing = internalGetThing(player, fromPos, fromStackPos, spriteId, STACKPOS_USEITEM); Thing* thing = internalGetThing(player, fromPos, fromStackPos, spriteId, STACKPOS_USEITEM);
if (!thing) { if (!thing) {
@ -2081,7 +2094,7 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin
player->resetIdleTime(); player->resetIdleTime();
player->setNextActionTask(nullptr); 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) void Game::playerCloseContainer(uint32_t playerId, uint8_t cid)
@ -2874,7 +2887,7 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
return; return;
} }
if (player->canWear(outfit.lookType)) { if (player->canWear(outfit.lookType, outfit.lookAddons)) {
player->defaultOutfit = outfit; player->defaultOutfit = outfit;
if (player->hasCondition(CONDITION_OUTFIT)) { if (player->hasCondition(CONDITION_OUTFIT)) {

77
src/outfit.cpp Normal file
View File

@ -0,0 +1,77 @@
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
*
* 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<uint16_t>(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<uint16_t>(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;
}

63
src/outfit.h Normal file
View File

@ -0,0 +1,63 @@
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
*
* 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<Outfit>& getOutfits(PlayerSex_t sex) const {
return outfits[sex];
}
private:
std::vector<Outfit> outfits[PLAYERSEX_LAST + 1];
};
#endif

View File

@ -493,6 +493,11 @@ uint16_t Player::getContainerIndex(uint8_t cid) const
return it->second.index; return it->second.index;
} }
bool Player::canOpenCorpse(uint32_t ownerId) const
{
return getID() == ownerId || (party && party->canOpenCorpse(ownerId));
}
uint16_t Player::getLookCorpse() const uint16_t Player::getLookCorpse() const
{ {
if (sex == PLAYERSEX_FEMALE) { if (sex == PLAYERSEX_FEMALE) {
@ -3129,7 +3134,7 @@ bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/)
void Player::gainExperience(uint64_t gainExp, Creature* source) void Player::gainExperience(uint64_t gainExp, Creature* source)
{ {
if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0) { if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0 || staminaMinutes == 0) {
return; return;
} }
@ -3223,26 +3228,31 @@ void Player::changeSoul(int32_t soulChange)
sendStats(); sendStats();
} }
bool Player::canWear(uint32_t lookType) const bool Player::canWear(uint32_t lookType, uint8_t addons) const
{ {
if (group->access) { if (group->access) {
return true; return true;
} }
if (getSex() == PLAYERSEX_MALE) { const Outfit* outfit = Outfits::getInstance().getOutfitByLookType(sex, lookType);
if (lookType >= 132 && lookType <= 134 && isPremium()) { if (!outfit) {
return true; return false;
} 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;
}
} }
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; return false;
} }
@ -3263,6 +3273,77 @@ bool Player::canLogout()
return !isPzLocked() && !hasCondition(CONDITION_INFIGHT); 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) void Player::setSex(PlayerSex_t newSex)
{ {
sex = newSex; sex = newSex;

View File

@ -23,6 +23,7 @@
#include "creature.h" #include "creature.h"
#include "container.h" #include "container.h"
#include "cylinder.h" #include "cylinder.h"
#include "outfit.h"
#include "enums.h" #include "enums.h"
#include "vocation.h" #include "vocation.h"
#include "protocolgame.h" #include "protocolgame.h"
@ -77,9 +78,10 @@ struct OpenContainer {
}; };
struct OutfitEntry { struct OutfitEntry {
constexpr OutfitEntry(uint16_t lookType) : lookType(lookType) {} constexpr OutfitEntry(uint16_t lookType, uint8_t addons) : lookType(lookType), addons(addons) {}
uint16_t lookType; uint16_t lookType;
uint8_t addons;
}; };
struct Skill { struct Skill {
@ -148,6 +150,10 @@ class Player final : public Creature, public Cylinder
return ((50ULL * lv * lv * lv) - (150ULL * lv * lv) + (400ULL * lv)) / 3ULL; return ((50ULL * lv * lv * lv) - (150ULL * lv * lv) + (400ULL * lv)) / 3ULL;
} }
uint16_t getStaminaMinutes() const {
return staminaMinutes;
}
uint64_t getBankBalance() const { uint64_t getBankBalance() const {
return bankBalance; return bankBalance;
} }
@ -265,8 +271,11 @@ class Player final : public Creature, public Cylinder
int8_t getContainerID(const Container* container) const; int8_t getContainerID(const Container* container) const;
uint16_t getContainerIndex(uint8_t cid) const; uint16_t getContainerIndex(uint8_t cid) const;
bool canOpenCorpse(uint32_t ownerId) const;
void addStorageValue(const uint32_t key, const int32_t value); void addStorageValue(const uint32_t key, const int32_t value);
bool getStorageValue(const uint32_t key, int32_t& value) const; bool getStorageValue(const uint32_t key, int32_t& value) const;
void genReservedStorageRange();
void setGroup(Group* newGroup) { void setGroup(Group* newGroup) {
group = newGroup; group = newGroup;
@ -556,7 +565,12 @@ class Player final : public Creature, public Cylinder
} }
void checkSkullTicks(); 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(); bool canLogout();
@ -1032,6 +1046,7 @@ class Player final : public Creature, public Cylinder
int32_t shieldBlockCount = 0; int32_t shieldBlockCount = 0;
int32_t idleTime = 0; int32_t idleTime = 0;
uint16_t staminaMinutes = 2520;
uint16_t maxWriteLen = 0; uint16_t maxWriteLen = 0;
uint8_t soul = 0; uint8_t soul = 0;

View File

@ -22,6 +22,7 @@
#include "protocol.h" #include "protocol.h"
#include "outputmessage.h" #include "outputmessage.h"
#include "rsa.h" #include "rsa.h"
#include "xtea.h"
extern RSA g_RSA; extern RSA g_RSA;
@ -32,7 +33,7 @@ void Protocol::onSendMessage(const OutputMessage_ptr& msg) const
if (encryptionEnabled) { if (encryptionEnabled) {
XTEA_encrypt(*msg); XTEA_encrypt(*msg);
msg->addCryptoHeader(); msg->addCryptoHeader(checksumEnabled);
} }
} }
} }
@ -51,7 +52,8 @@ OutputMessage_ptr Protocol::getOutputBuffer(int32_t size)
//dispatcher thread //dispatcher thread
if (!outputBuffer) { if (!outputBuffer) {
outputBuffer = OutputMessagePool::getOutputMessage(); 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); send(outputBuffer);
outputBuffer = OutputMessagePool::getOutputMessage(); outputBuffer = OutputMessagePool::getOutputMessage();
} }
@ -60,73 +62,27 @@ OutputMessage_ptr Protocol::getOutputBuffer(int32_t size)
void Protocol::XTEA_encrypt(OutputMessage& msg) const void Protocol::XTEA_encrypt(OutputMessage& msg) const
{ {
const uint32_t delta = 0x61C88647;
// The message must be a multiple of 8 // The message must be a multiple of 8
size_t paddingBytes = msg.getLength() % 8; size_t paddingBytes = msg.getLength() % 8u;
if (paddingBytes != 0) { if (paddingBytes != 0) {
msg.addPaddingBytes(8 - paddingBytes); msg.addPaddingBytes(8 - paddingBytes);
} }
uint8_t* buffer = msg.getOutputBuffer(); uint8_t* buffer = msg.getOutputBuffer();
const size_t messageLength = msg.getLength(); xtea::encrypt(buffer, msg.getLength(), key);
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;
}
} }
bool Protocol::XTEA_decrypt(NetworkMessage& msg) const bool Protocol::XTEA_decrypt(NetworkMessage& msg) const
{ {
if (((msg.getLength() - 2) % 8) != 0) { if (((msg.getLength() - 6) & 7) != 0) {
return false; return false;
} }
const uint32_t delta = 0x61C88647;
uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition(); uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition();
const size_t messageLength = (msg.getLength() - 6); xtea::decrypt(buffer, msg.getLength() - 6, key);
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 = 0xC6EF3720; uint16_t innerLength = msg.get<uint16_t>();
if (innerLength + 8 > msg.getLength()) {
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<uint16_t>();
if (innerLength > msg.getLength() - 4) {
return false; return false;
} }

View File

@ -21,6 +21,7 @@
#define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1 #define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1
#include "connection.h" #include "connection.h"
#include "xtea.h"
class Protocol : public std::enable_shared_from_this<Protocol> class Protocol : public std::enable_shared_from_this<Protocol>
{ {
@ -71,12 +72,13 @@ class Protocol : public std::enable_shared_from_this<Protocol>
void enableXTEAEncryption() { void enableXTEAEncryption() {
encryptionEnabled = true; encryptionEnabled = true;
} }
void setXTEAKey(const uint32_t* key) { void setXTEAKey(xtea::key key) {
memcpy(this->key, key, sizeof(*key) * 4); this->key = std::move(key);
}
void disableChecksum() {
checksumEnabled = false;
} }
void XTEA_encrypt(OutputMessage& msg) const;
bool XTEA_decrypt(NetworkMessage& msg) const;
static bool RSA_decrypt(NetworkMessage& msg); static bool RSA_decrypt(NetworkMessage& msg);
void setRawMessages(bool value) { void setRawMessages(bool value) {
@ -84,13 +86,19 @@ class Protocol : public std::enable_shared_from_this<Protocol>
} }
virtual void release() {} virtual void release() {}
private:
void XTEA_encrypt(OutputMessage& msg) const;
bool XTEA_decrypt(NetworkMessage& msg) const;
friend class Connection; friend class Connection;
OutputMessage_ptr outputBuffer; OutputMessage_ptr outputBuffer;
private:
const ConnectionWeak_ptr connection; const ConnectionWeak_ptr connection;
uint32_t key[4] = {}; xtea::key key;
bool encryptionEnabled = false; bool encryptionEnabled = false;
bool checksumEnabled = true;
bool rawMessages = false; bool rawMessages = false;
}; };

View File

@ -242,18 +242,20 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
OperatingSystem_t operatingSystem = static_cast<OperatingSystem_t>(msg.get<uint16_t>()); OperatingSystem_t operatingSystem = static_cast<OperatingSystem_t>(msg.get<uint16_t>());
version = msg.get<uint16_t>(); version = msg.get<uint16_t>();
msg.skipBytes(7); // U32 client version, U8 client type, U16 dat revision
if (!Protocol::RSA_decrypt(msg)) { if (!Protocol::RSA_decrypt(msg)) {
disconnect(); disconnect();
return; return;
} }
uint32_t key[4]; xtea::key key;
key[0] = msg.get<uint32_t>(); key[0] = msg.get<uint32_t>();
key[1] = msg.get<uint32_t>(); key[1] = msg.get<uint32_t>();
key[2] = msg.get<uint32_t>(); key[2] = msg.get<uint32_t>();
key[3] = msg.get<uint32_t>(); key[3] = msg.get<uint32_t>();
enableXTEAEncryption(); enableXTEAEncryption();
setXTEAKey(key); setXTEAKey(std::move(key));
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) { if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
NetworkMessage opcodeMessage; NetworkMessage opcodeMessage;
@ -315,12 +317,32 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem))); g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem)));
} }
void ProtocolGame::sendUpdateRequest() void ProtocolGame::onConnect()
{ {
auto output = OutputMessagePool::getOutputMessage(); auto output = OutputMessagePool::getOutputMessage();
output->addByte(0x11); static std::random_device rd;
static std::ranlux24 generator(rd());
static std::uniform_int_distribution<uint16_t> randNumber(0x00, 0xFF);
// Skip checksum
output->skipBytes(sizeof(uint32_t));
// Packet length & type
output->add<uint16_t>(0x0006);
output->addByte(0x1F);
// Add timestamp & random number
challengeTimestamp = static_cast<uint32_t>(time(nullptr));
output->add<uint32_t>(challengeTimestamp);
challengeRandom = randNumber(generator);
output->addByte(challengeRandom);
// Go back and write checksum
output->skipBytes(-12);
output->add<uint32_t>(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8));
send(output); send(output);
disconnect();
} }
void ProtocolGame::disconnectClient(const std::string& message) const void ProtocolGame::disconnectClient(const std::string& message) const
@ -693,6 +715,7 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg)
newOutfit.lookBody = msg.getByte(); newOutfit.lookBody = msg.getByte();
newOutfit.lookLegs = msg.getByte(); newOutfit.lookLegs = msg.getByte();
newOutfit.lookFeet = msg.getByte(); newOutfit.lookFeet = msg.getByte();
newOutfit.lookAddons = msg.getByte();
addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit); addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit);
} }
@ -1715,20 +1738,31 @@ void ProtocolGame::sendOutfitWindow()
Outfit_t currentOutfit = player->getDefaultOutfit(); Outfit_t currentOutfit = player->getDefaultOutfit();
AddOutfit(msg, currentOutfit); AddOutfit(msg, currentOutfit);
if (player->getSex() == PLAYERSEX_MALE) { std::vector<ProtocolOutfit> protocolOutfits;
msg.add<uint16_t>(128); if (player->isAccessPlayer()) {
if (player->isPremium()) { static const std::string gamemasterOutfitName = "Gamemaster";
msg.add<uint16_t>(134); protocolOutfits.emplace_back(gamemasterOutfitName, 75, 0);
} else {
msg.add<uint16_t>(131);
} }
} else {
msg.add<uint16_t>(136); const auto& outfits = Outfits::getInstance().getOutfits(player->getSex());
if (player->isPremium()) { protocolOutfits.reserve(outfits.size());
msg.add<uint16_t>(142); for (const Outfit& outfit : outfits) {
} else { uint8_t addons;
msg.add<uint16_t>(139); 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<uint16_t>(outfit.lookType);
msg.addString(outfit.name);
msg.addByte(outfit.addons);
} }
writeToOutputBuffer(msg); writeToOutputBuffer(msg);
@ -1810,14 +1844,22 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
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->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<uint16_t>(std::min<int32_t>(player->getMaxHealth(), std::numeric_limits<uint16_t>::max()));
msg.add<uint16_t>(static_cast<uint16_t>(player->getFreeCapacity() / 100.)); msg.add<uint32_t>(player->getFreeCapacity());
if (player->getExperience() >= std::numeric_limits<uint32_t>::max()) { msg.add<uint32_t>(player->getCapacity());
msg.add<uint32_t>(0);
} else { msg.add<uint64_t>(player->getExperience());
msg.add<uint32_t>(static_cast<uint32_t>(player->getExperience()));
} msg.add<uint16_t>(player->getLevel());
msg.add<uint16_t>(static_cast<uint16_t>(player->getLevel()));
msg.addByte(player->getLevelPercent()); 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->getMana(), std::numeric_limits<uint16_t>::max()));
msg.add<uint16_t>(std::min<int32_t>(player->getMaxMana(), std::numeric_limits<uint16_t>::max())); msg.add<uint16_t>(std::min<int32_t>(player->getMaxMana(), std::numeric_limits<uint16_t>::max()));
@ -1825,6 +1867,10 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
msg.addByte(player->getMagicLevelPercent()); msg.addByte(player->getMagicLevelPercent());
msg.addByte(player->getSoul()); msg.addByte(player->getSoul());
msg.add<uint16_t>(player->getStaminaMinutes());
msg.add<uint16_t>(0); // xp boost time (seconds)
} }
void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) 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.lookBody);
msg.addByte(outfit.lookLegs); msg.addByte(outfit.lookLegs);
msg.addByte(outfit.lookFeet); msg.addByte(outfit.lookFeet);
msg.addByte(outfit.lookAddons);
} else { } else {
msg.addItemId(outfit.lookTypeEx); msg.addItemId(outfit.lookTypeEx);
} }

View File

@ -50,8 +50,9 @@ class ProtocolGame final : public Protocol
{ {
public: public:
// static protocol information // static protocol information
enum {server_sends_first = false}; enum { server_sends_first = true };
enum {protocol_identifier = 0x0A}; // Not required as we send first enum { protocol_identifier = 0 }; // Not required as we send first
enum { use_checksum = true };
static const char* protocol_name() { static const char* protocol_name() {
return "gameworld protocol"; return "gameworld protocol";
@ -71,7 +72,6 @@ class ProtocolGame final : public Protocol
return std::static_pointer_cast<ProtocolGame>(shared_from_this()); return std::static_pointer_cast<ProtocolGame>(shared_from_this());
} }
void connect(uint32_t playerId, OperatingSystem_t operatingSystem); void connect(uint32_t playerId, OperatingSystem_t operatingSystem);
void sendUpdateRequest();
void disconnectClient(const std::string& message) const; void disconnectClient(const std::string& message) const;
void writeToOutputBuffer(const NetworkMessage& msg); void writeToOutputBuffer(const NetworkMessage& msg);
@ -86,6 +86,7 @@ class ProtocolGame final : public Protocol
// we have all the parse methods // we have all the parse methods
void parsePacket(NetworkMessage& msg) final; void parsePacket(NetworkMessage& msg) final;
void onRecvFirstMessage(NetworkMessage& msg) final; void onRecvFirstMessage(NetworkMessage& msg) final;
void onConnect() override;
//Parse methods //Parse methods
void parseAutoWalk(NetworkMessage& msg); void parseAutoWalk(NetworkMessage& msg);
@ -262,8 +263,11 @@ class ProtocolGame final : public Protocol
Player* player = nullptr; Player* player = nullptr;
uint32_t eventConnect = 0; uint32_t eventConnect = 0;
uint32_t challengeTimestamp = 0;
uint16_t version = CLIENT_VERSION_MIN; uint16_t version = CLIENT_VERSION_MIN;
uint8_t challengeRandom = 0;
bool debugAssertSent = false; bool debugAssertSent = false;
bool acceptPackets = false; bool acceptPackets = false;
}; };

View File

@ -32,36 +32,39 @@
extern ConfigManager g_config; extern ConfigManager g_config;
extern Game g_game; extern Game g_game;
void ProtocolLogin::sendUpdateRequest() void ProtocolLogin::disconnectClient(const std::string& message, uint16_t version)
{ {
auto output = OutputMessagePool::getOutputMessage(); auto output = OutputMessagePool::getOutputMessage();
output->addByte(0x1E); output->addByte(version >= 1076 ? 0x0B : 0x0A);
send(output);
disconnect();
}
void ProtocolLogin::disconnectClient(const std::string& message)
{
auto output = OutputMessagePool::getOutputMessage();
output->addByte(0x0A);
output->addString(message); output->addString(message);
send(output); send(output);
disconnect(); 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; Account account;
if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) { if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) {
disconnectClient("Accountnumber or password is not correct."); disconnectClient("Accountnumber or password is not correct.", version);
return; return;
} }
uint32_t ticks = time(nullptr) / AUTHENTICATOR_PERIOD;
auto output = OutputMessagePool::getOutputMessage(); 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 //Update premium days
Game::updatePremium(account); Game::updatePremium(account);
@ -76,23 +79,38 @@ void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string&
output->addString(ss.str()); 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 //Add char list
output->addByte(0x64); 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<uint16_t>(g_config.getNumber(ConfigManager::GAME_PORT));
output->addByte(0);
uint8_t size = std::min<size_t>(std::numeric_limits<uint8_t>::max(), account.characters.size()); uint8_t size = std::min<size_t>(std::numeric_limits<uint8_t>::max(), account.characters.size());
output->addByte(size); output->addByte(size);
for (uint8_t i = 0; i < size; i++) { for (uint8_t i = 0; i < size; i++) {
output->addByte(0);
output->addString(account.characters[i]); output->addString(account.characters[i]);
output->addString(g_config.getString(ConfigManager::SERVER_NAME));
output->add<uint32_t>(inet_addr(g_config.getString(ConfigManager::IP).c_str()));
output->add<uint16_t>(g_config.getNumber(ConfigManager::GAME_PORT));
} }
//Add premium days //Add premium days
output->addByte(0);
if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) {
output->add<uint16_t>(0xFFFF); output->addByte(1);
} else { output->add<uint32_t>(0);
output->add<uint16_t>(account.premiumDays); }
else {
output->addByte(account.premiumDays > 0 ? 1 : 0);
output->add<uint32_t>(time(nullptr) + (account.premiumDays * 86400));
} }
send(output); send(output);
@ -109,41 +127,55 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
msg.skipBytes(2); // client OS msg.skipBytes(2); // client OS
/*uint16_t version =*/ msg.get<uint16_t>(); uint16_t version = msg.get<uint16_t>();
if (version >= 971) {
msg.skipBytes(17);
}
else {
msg.skipBytes(12); msg.skipBytes(12);
}
/* /*
* Skipped bytes: * Skipped bytes:
* 4 bytes: protocolVersion * 4 bytes: protocolVersion
* 12 bytes: dat, spr, pic signatures (4 bytes each) * 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)) { if (!Protocol::RSA_decrypt(msg)) {
disconnect(); disconnect();
return; return;
} }
uint32_t key[4]; xtea::key key;
key[0] = msg.get<uint32_t>(); key[0] = msg.get<uint32_t>();
key[1] = msg.get<uint32_t>(); key[1] = msg.get<uint32_t>();
key[2] = msg.get<uint32_t>(); key[2] = msg.get<uint32_t>();
key[3] = msg.get<uint32_t>(); key[3] = msg.get<uint32_t>();
enableXTEAEncryption(); enableXTEAEncryption();
setXTEAKey(key); setXTEAKey(std::move(key));
/*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) {
//sendUpdateRequest(); std::ostringstream ss;
disconnectClient("Use Tibia 7.72 to login!"); ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
disconnectClient(ss.str(), version);
return; return;
}*/ }
if (g_game.getGameState() == GAME_STATE_STARTUP) { if (g_game.getGameState() == GAME_STATE_STARTUP) {
disconnectClient("Gameworld is starting up. Please wait."); disconnectClient("Gameworld is starting up. Please wait.", version);
return; return;
} }
if (g_game.getGameState() == GAME_STATE_MAINTAIN) { 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; return;
} }
@ -160,7 +192,7 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
std::ostringstream ss; std::ostringstream ss;
ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; 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; return;
} }
@ -176,6 +208,15 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
return; return;
} }
auto thisPtr = std::static_pointer_cast<ProtocolLogin>(shared_from_this()); // read authenticator token and stay logged in flag from last 128 bytes
g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password))); 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<ProtocolLogin>(shared_from_this());
g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password, version)));
} }

View File

@ -38,13 +38,12 @@ class ProtocolLogin : public Protocol
explicit ProtocolLogin(Connection_ptr connection) : Protocol(connection) {} explicit ProtocolLogin(Connection_ptr connection) : Protocol(connection) {}
void onRecvFirstMessage(NetworkMessage& msg); void onRecvFirstMessage(NetworkMessage& msg) override;
protected: private:
void sendUpdateRequest(); void disconnectClient(const std::string& message, uint16_t version);
void disconnectClient(const std::string& message);
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 #endif

View File

@ -21,72 +21,60 @@
#include "rsa.h" #include "rsa.h"
RSA::RSA() #include <cryptopp/base64.h>
{ #include <cryptopp/osrng.h>
mpz_init(n);
mpz_init2(d, 1024);
}
RSA::~RSA() #include <fstream>
{ #include <sstream>
mpz_clear(n);
mpz_clear(d);
}
void RSA::setKey(const char* pString, const char* qString) static CryptoPP::AutoSeededRandomPool prng;
{
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 void RSA::decrypt(char* msg) const
{ {
mpz_t c, m; CryptoPP::Integer m{ reinterpret_cast<uint8_t*>(msg), 128 };
mpz_init2(c, 1024); auto c = pk.CalculateInverse(prng, m);
mpz_init2(m, 1024); c.Encode(reinterpret_cast<uint8_t*>(msg), 128);
}
mpz_import(c, 128, 1, 1, 0, 0, msg);
static const std::string header = "-----BEGIN RSA PRIVATE KEY-----";
// m = c^d mod n static const std::string footer = "-----END RSA PRIVATE KEY-----";
mpz_powm(m, c, d, n);
void RSA::loadPEM(const std::string& filename)
size_t count = (mpz_sizeinbase(m, 2) + 7) / 8; {
memset(msg, 0, 128 - count); std::ifstream file{ filename };
mpz_export(msg + (128 - count), nullptr, 1, 1, 0, 0, m);
if (!file.is_open()) {
mpz_clear(c); throw std::runtime_error("Missing file " + filename + ".");
mpz_clear(m); }
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<const uint8_t*>(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';
}
} }

View File

@ -20,24 +20,24 @@
#ifndef FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91 #ifndef FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91
#define FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91 #define FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91
#include <gmp.h> #include <cryptopp/rsa.h>
#include <string>
class RSA class RSA
{ {
public: public:
RSA(); RSA() = default;
~RSA();
// non-copyable // non-copyable
RSA(const RSA&) = delete; RSA(const RSA&) = delete;
RSA& operator=(const RSA&) = delete; RSA& operator=(const RSA&) = delete;
void setKey(const char* pString, const char* qString); void loadPEM(const std::string& filename);
void decrypt(char* msg) const; void decrypt(char* msg) const;
private: private:
//use only GMP CryptoPP::RSA::PrivateKey pk;
mpz_t n, d;
}; };
#endif #endif

View File

@ -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(); uint8_t protocolID = msg.getByte();
for (auto& service : services) { for (auto& service : services) {
@ -140,8 +140,10 @@ Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_pt
continue; continue;
} }
if ((checksummed && service->is_checksummed()) || !service->is_checksummed()) {
return service->make_protocol(connection); return service->make_protocol(connection);
} }
}
return nullptr; return nullptr;
} }

View File

@ -29,6 +29,7 @@ class ServiceBase
{ {
public: public:
virtual bool is_single_socket() const = 0; virtual bool is_single_socket() const = 0;
virtual bool is_checksummed() const = 0;
virtual uint8_t get_protocol_identifier() const = 0; virtual uint8_t get_protocol_identifier() const = 0;
virtual const char* get_protocol_name() const = 0; virtual const char* get_protocol_name() const = 0;
@ -39,17 +40,20 @@ template <typename ProtocolType>
class Service final : public ServiceBase class Service final : public ServiceBase
{ {
public: public:
bool is_single_socket() const final { bool is_single_socket() const override {
return ProtocolType::server_sends_first; 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; return ProtocolType::protocol_identifier;
} }
const char* get_protocol_name() const final { const char* get_protocol_name() const override {
return ProtocolType::protocol_name(); 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<ProtocolType>(c); return std::make_shared<ProtocolType>(c);
} }
}; };
@ -71,7 +75,7 @@ class ServicePort : public std::enable_shared_from_this<ServicePort>
std::string get_protocol_names() const; std::string get_protocol_names() const;
bool add_service(const Service_ptr& new_svc); 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 onStopServer();
void onAccept(Connection_ptr connection, const boost::system::error_code& error); void onAccept(Connection_ptr connection, const boost::system::error_code& error);

View File

@ -272,6 +272,51 @@ std::string pluralizeString(std::string str)
return str2; 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<char>(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<char>(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<uint32_t>(std::strtoul(message.substr(39, 1).c_str(), nullptr, 16) & 0xF);
// get truncated hash
uint32_t truncHash = static_cast<uint32_t>(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) void replaceString(std::string& str, const std::string& sought, const std::string& replacement)
{ {
size_t pos = 0; 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) std::string ucfirst(std::string str)
{ {
for (char& i : str) { for (char& i : str) {

View File

@ -30,6 +30,7 @@
void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result); 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 transformToSHA1(const std::string& input);
std::string generateToken(const std::string& key, uint32_t ticks);
uint8_t getLiquidColor(uint8_t type); uint8_t getLiquidColor(uint8_t type);
void extractArticleAndName(std::string& data, std::string& article, std::string& name); 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); std::string getSkillName(uint8_t skillid);
uint32_t adlerChecksum(const uint8_t* data, size_t length);
std::string ucfirst(std::string str); std::string ucfirst(std::string str);
std::string ucwords(std::string str); std::string ucwords(std::string str);
bool booleanString(const std::string& str); bool booleanString(const std::string& str);

140
src/xtea.cpp Normal file
View File

@ -0,0 +1,140 @@
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
*
* 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 <array>
#include <assert.h>
namespace xtea {
namespace {
constexpr uint32_t delta = 0x9E3779B9;
template<size_t BLOCK_SIZE>
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<uint8_t>(left[i]);
data[j+1] = static_cast<uint8_t>(left[i] >> 8u);
data[j+2] = static_cast<uint8_t>(left[i] >> 16u);
data[j+3] = static_cast<uint8_t>(left[i] >> 24u);
data[j+4] = static_cast<uint8_t>(right[i]);
data[j+5] = static_cast<uint8_t>(right[i] >> 8u);
data[j+6] = static_cast<uint8_t>(right[i] >> 16u);
data[j+7] = static_cast<uint8_t>(right[i] >> 24u);
}
}
template<size_t BLOCK_SIZE>
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<uint8_t>(left[i]);
data[j+1] = static_cast<uint8_t>(left[i] >> 8u);
data[j+2] = static_cast<uint8_t>(left[i] >> 16u);
data[j+3] = static_cast<uint8_t>(left[i] >> 24u);
data[j+4] = static_cast<uint8_t>(right[i]);
data[j+5] = static_cast<uint8_t>(right[i] >> 8u);
data[j+6] = static_cast<uint8_t>(right[i] >> 16u);
data[j+7] = static_cast<uint8_t>(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<bool Encrypt, size_t BlockSize>
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<BlockSize>(input + i, k);
} else {
XTEA_decrypt<BlockSize>(input + i, k);
}
}
input += blocks;
length -= blocks;
if (BlockSize != 1) {
XTEA<Encrypt, (BlockSize + 1u) / 2u>()(input, length, k);
}
}
};
constexpr auto encrypt_v = XTEA<true, InitialBlockSize>();
constexpr auto decrypt_v = XTEA<false, InitialBlockSize>();
} // 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

32
src/xtea.h Normal file
View File

@ -0,0 +1,32 @@
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
*
* 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<uint32_t, 4>;
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

View File

@ -190,6 +190,7 @@
<PrecompiledHeaderFile>otpch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>otpch.h</PrecompiledHeaderFile>
</ClCompile> </ClCompile>
<ClCompile Include="..\src\otserv.cpp" /> <ClCompile Include="..\src\otserv.cpp" />
<ClCompile Include="..\src\outfit.cpp" />
<ClCompile Include="..\src\outputmessage.cpp" /> <ClCompile Include="..\src\outputmessage.cpp" />
<ClCompile Include="..\src\party.cpp" /> <ClCompile Include="..\src\party.cpp" />
<ClCompile Include="..\src\player.cpp" /> <ClCompile Include="..\src\player.cpp" />
@ -215,6 +216,7 @@
<ClCompile Include="..\src\vocation.cpp" /> <ClCompile Include="..\src\vocation.cpp" />
<ClCompile Include="..\src\waitlist.cpp" /> <ClCompile Include="..\src\waitlist.cpp" />
<ClCompile Include="..\src\wildcardtree.cpp" /> <ClCompile Include="..\src\wildcardtree.cpp" />
<ClCompile Include="..\src\xtea.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\src\account.h" /> <ClInclude Include="..\src\account.h" />
@ -263,6 +265,7 @@
<ClInclude Include="..\src\networkmessage.h" /> <ClInclude Include="..\src\networkmessage.h" />
<ClInclude Include="..\src\npc.h" /> <ClInclude Include="..\src\npc.h" />
<ClInclude Include="..\src\otpch.h" /> <ClInclude Include="..\src\otpch.h" />
<ClInclude Include="..\src\outfit.h" />
<ClInclude Include="..\src\outputmessage.h" /> <ClInclude Include="..\src\outputmessage.h" />
<ClInclude Include="..\src\party.h" /> <ClInclude Include="..\src\party.h" />
<ClInclude Include="..\src\player.h" /> <ClInclude Include="..\src\player.h" />
@ -291,6 +294,7 @@
<ClInclude Include="..\src\vocation.h" /> <ClInclude Include="..\src\vocation.h" />
<ClInclude Include="..\src\waitlist.h" /> <ClInclude Include="..\src\waitlist.h" />
<ClInclude Include="..\src\wildcardtree.h" /> <ClInclude Include="..\src\wildcardtree.h" />
<ClInclude Include="..\src\xtea.h" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">