mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-04-30 09:39:20 +02:00
Merge branch '3-upgrade-client-protocol-to-7-81-actual' into 'master'
Resolve "Upgrade client protocol to 7.81" [Actual Changes] Closes #3 See merge request ErikasKontenis/Sabrehaven!9
This commit is contained in:
commit
c01ff81bff
@ -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)
|
||||
|
35
data/XML/outfits.xml
Normal file
35
data/XML/outfits.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?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" />
|
||||
<!-- 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" />
|
||||
</outfits>
|
2
data/world/mymap-house.xml
Normal file
2
data/world/mymap-house.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<houses/>
|
2
data/world/mymap-spawn.xml
Normal file
2
data/world/mymap-spawn.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<spawns/>
|
BIN
data/world/mymap.otbm
Normal file
BIN
data/world/mymap.otbm
Normal file
Binary file not shown.
13
key.pem
Normal file
13
key.pem
Normal file
@ -0,0 +1,13 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQCbZGkDtFsHrJVlaNhzU71xZROd15QHA7A+bdB5OZZhtKg3qmBWHXzLlFL6AIBZ
|
||||
SQmIKrW8pYoaGzX4sQWbcrEhJhHGFSrT27PPvuetwUKnXT11lxUJwyHFwkpb1R/UYPAbThW+sN4Z
|
||||
MFKKXT8VwePL9cQB1nd+EKyqsz2+jVt/9QIDAQABAoGAQovTtTRtr3GnYRBvcaQxAvjIV9ZUnFRm
|
||||
C7Y3i1KwJhOZ3ozmSLrEEOLqTgoc7R+sJ1YzEiDKbbete11EC3gohlhW56ptj0WDf+7ptKOgqiEy
|
||||
Kh4qt1sYJeeGz4GiiooJoeKFGdtk/5uvMR6FDCv6H7ewigVswzf330Q3Ya7+jYECQQERBxsga6+5
|
||||
x6IofXyNF6QuMqvuiN/pUgaStUOdlnWBf/T4yUpKvNS1+I4iDzqGWOOSR6RsaYPYVhj9iRABoKyx
|
||||
AkEAkbNzB6vhLAWht4dUdGzaREF3p4SwNcu5bJRa/9wCLSHaS9JaTq4lljgVPp1zyXyJCSCWpFnl
|
||||
0WvK3Qf6nVBIhQJBANS7rK8+ONWQbxENdZaZ7Rrx8HUTwSOS/fwhsGWBbl1Qzhdq/6/sIfEHkfeH
|
||||
1hoH+IlpuPuf21MdAqvJt+cMwoECQF1LyBOYduYGcSgg6u5mKVldhm3pJCA+ZGxnjuGZEnet3qeA
|
||||
eb05++112fyvO85ABUun524z9lokKNFh45NKLjUCQGshzV43P+RioiBhtEpB/QFzijiS4L2HKNu1
|
||||
tdhudnUjWkaf6jJmQS/ppln0hhRMHlk9Vus/bPx7LtuDuo6VQDo=
|
||||
-----END RSA PRIVATE KEY-----
|
@ -1125,6 +1125,7 @@ CREATE TABLE `players` (
|
||||
`lookhead` int(11) NOT NULL DEFAULT '0',
|
||||
`looklegs` int(11) NOT NULL DEFAULT '0',
|
||||
`looktype` int(11) NOT NULL DEFAULT '136',
|
||||
`lookaddons` int(11) NOT NULL DEFAULT '0',
|
||||
`maglevel` int(11) NOT NULL DEFAULT '0',
|
||||
`mana` int(11) NOT NULL DEFAULT '0',
|
||||
`manamax` int(11) NOT NULL DEFAULT '0',
|
||||
@ -1147,6 +1148,7 @@ CREATE TABLE `players` (
|
||||
`onlinetime` int(11) NOT NULL DEFAULT '0',
|
||||
`deletion` bigint(15) NOT NULL DEFAULT '0',
|
||||
`balance` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`stamina` smallint(5) NOT NULL DEFAULT '3360',
|
||||
`skill_fist` int(10) UNSIGNED NOT NULL DEFAULT '10',
|
||||
`skill_fist_tries` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`skill_club` int(10) UNSIGNED NOT NULL DEFAULT '10',
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -24,6 +24,10 @@
|
||||
#include "enums.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
|
||||
{
|
||||
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<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 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<uint16_t> ids;
|
||||
std::vector<uint16_t> uids;
|
||||
std::vector<uint16_t> 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;
|
||||
|
@ -68,11 +68,13 @@ 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);
|
||||
boolean[REPLACE_KICK_ON_LOGIN] = getGlobalBoolean(L, "replaceKickOnLogin", true);
|
||||
boolean[ALLOW_CLONES] = getGlobalBoolean(L, "allowClones", false);
|
||||
boolean[STAMINA_SYSTEM] = getGlobalBoolean(L, "staminaSystem", true);
|
||||
boolean[WARN_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "warnUnsafeScripts", true);
|
||||
boolean[CONVERT_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "convertUnsafeScripts", true);
|
||||
boolean[TELEPORT_NEWBIES] = getGlobalBoolean(L, "teleportNewbies", true);
|
||||
|
@ -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,
|
||||
@ -36,6 +37,7 @@ class ConfigManager
|
||||
ALLOW_CLONES,
|
||||
BIND_ONLY_GLOBAL_ADDRESS,
|
||||
OPTIMIZE_DATABASE,
|
||||
STAMINA_SYSTEM,
|
||||
WARN_UNSAFE_SCRIPTS,
|
||||
CONVERT_UNSAFE_SCRIPTS,
|
||||
TELEPORT_NEWBIES,
|
||||
|
@ -182,7 +182,8 @@ void Connection::parsePacket(const boost::system::error_code& error)
|
||||
if (error) {
|
||||
close(FORCE_CLOSE);
|
||||
return;
|
||||
} else if (connectionState != CONNECTION_STATE_OPEN) {
|
||||
}
|
||||
else if (connectionState != CONNECTION_STATE_OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -197,30 +198,35 @@ void Connection::parsePacket(const boost::system::error_code& error)
|
||||
close(FORCE_CLOSE);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
msg.skipBytes(1); // Skip protocol ID
|
||||
}
|
||||
|
||||
protocol->onRecvFirstMessage(msg);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
protocol->onRecvMessage(msg); // Send the packet to the current protocol
|
||||
}
|
||||
|
||||
try {
|
||||
readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT));
|
||||
readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
|
||||
std::placeholders::_1));
|
||||
std::placeholders::_1));
|
||||
|
||||
// Wait to the next packet
|
||||
boost::asio::async_read(socket,
|
||||
boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
|
||||
std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
|
||||
} catch (boost::system::system_error& e) {
|
||||
boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
|
||||
std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
|
||||
}
|
||||
catch (boost::system::system_error& e) {
|
||||
std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl;
|
||||
close(FORCE_CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Connection::send(const OutputMessage_ptr& msg)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
|
||||
|
10
src/const.h
10
src/const.h
@ -335,4 +335,14 @@ static constexpr int32_t CHANNEL_PARTY = 0x01;
|
||||
static constexpr int32_t CHANNEL_RULE_REP = 0x02;
|
||||
static constexpr int32_t CHANNEL_PRIVATE = 0xFFFF;
|
||||
|
||||
//Reserved player storage key ranges;
|
||||
//[10000000 - 20000000];
|
||||
static constexpr int32_t PSTRG_RESERVED_RANGE_START = 10000000;
|
||||
static constexpr int32_t PSTRG_RESERVED_RANGE_SIZE = 10000000;
|
||||
//[1000 - 1500];
|
||||
static constexpr int32_t PSTRG_OUTFITS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 1000);
|
||||
static constexpr int32_t PSTRG_OUTFITS_RANGE_SIZE = 500;
|
||||
|
||||
#define IS_IN_KEYRANGE(key, range) (key >= PSTRG_##range##_START && ((key - PSTRG_##range##_START) <= PSTRG_##range##_SIZE))
|
||||
|
||||
#endif
|
||||
|
@ -22,11 +22,14 @@
|
||||
|
||||
static constexpr auto STATUS_SERVER_NAME = "Sabrehaven";
|
||||
static constexpr auto STATUS_SERVER_VERSION = "1.0";
|
||||
static constexpr auto STATUS_SERVER_DEVELOPERS = "Sabrehaven Developers Team";
|
||||
static constexpr auto STATUS_SERVER_DEVELOPERS = "OTLand community & Sabrehaven Developers Team";
|
||||
|
||||
static constexpr auto CLIENT_VERSION_MIN = 772;
|
||||
static constexpr auto CLIENT_VERSION_MAX = 772;
|
||||
static constexpr auto CLIENT_VERSION_STR = "7.72";
|
||||
static constexpr auto CLIENT_VERSION_MIN = 781;
|
||||
static constexpr auto CLIENT_VERSION_MAX = 781;
|
||||
static constexpr auto CLIENT_VERSION_STR = "7.81";
|
||||
|
||||
static constexpr auto AUTHENTICATOR_DIGITS = 6U;
|
||||
static constexpr auto AUTHENTICATOR_PERIOD = 30U;
|
||||
|
||||
#ifndef __FUNCTION__
|
||||
#define __FUNCTION__ __func__
|
||||
|
@ -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 {
|
||||
|
29
src/game.cpp
29
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)) {
|
||||
|
@ -188,7 +188,7 @@ bool IOLoginData::preloadPlayer(Player* player, const std::string& name)
|
||||
bool IOLoginData::loadPlayerById(Player* player, uint32_t id)
|
||||
{
|
||||
std::ostringstream query;
|
||||
query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries` FROM `players` WHERE `id` = " << id;
|
||||
query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries` FROM `players` WHERE `id` = " << id;
|
||||
return loadPlayer(player, Database::getInstance()->storeQuery(query.str()));
|
||||
}
|
||||
|
||||
@ -196,7 +196,7 @@ bool IOLoginData::loadPlayerByName(Player* player, const std::string& name)
|
||||
{
|
||||
Database* db = Database::getInstance();
|
||||
std::ostringstream query;
|
||||
query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries` FROM `players` WHERE `name` = " << db->escapeString(name);
|
||||
query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries` FROM `players` WHERE `name` = " << db->escapeString(name);
|
||||
return loadPlayer(player, db->storeQuery(query.str()));
|
||||
}
|
||||
|
||||
@ -296,6 +296,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
|
||||
player->defaultOutfit.lookBody = result->getNumber<uint16_t>("lookbody");
|
||||
player->defaultOutfit.lookLegs = result->getNumber<uint16_t>("looklegs");
|
||||
player->defaultOutfit.lookFeet = result->getNumber<uint16_t>("lookfeet");
|
||||
player->defaultOutfit.lookAddons = result->getNumber<uint16_t>("lookaddons");
|
||||
player->currentOutfit = player->defaultOutfit;
|
||||
|
||||
if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) {
|
||||
@ -331,6 +332,8 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
|
||||
player->loginPosition = player->getTemplePosition();
|
||||
}
|
||||
|
||||
player->staminaMinutes = result->getNumber<uint16_t>("stamina");
|
||||
|
||||
static const std::string skillNames[] = {"skill_fist", "skill_club", "skill_sword", "skill_axe", "skill_dist", "skill_shielding", "skill_fishing"};
|
||||
static const std::string skillNameTries[] = {"skill_fist_tries", "skill_club_tries", "skill_sword_tries", "skill_axe_tries", "skill_dist_tries", "skill_shielding_tries", "skill_fishing_tries"};
|
||||
static constexpr size_t size = sizeof(skillNames) / sizeof(std::string);
|
||||
@ -616,6 +619,7 @@ bool IOLoginData::savePlayer(Player* player)
|
||||
query << "`lookhead` = " << static_cast<uint32_t>(player->defaultOutfit.lookHead) << ',';
|
||||
query << "`looklegs` = " << static_cast<uint32_t>(player->defaultOutfit.lookLegs) << ',';
|
||||
query << "`looktype` = " << player->defaultOutfit.lookType << ',';
|
||||
query << "`lookaddons` = " << static_cast<uint32_t>(player->defaultOutfit.lookAddons) << ',';
|
||||
query << "`maglevel` = " << player->magLevel << ',';
|
||||
query << "`mana` = " << player->mana << ',';
|
||||
query << "`manamax` = " << player->manaMax << ',';
|
||||
@ -654,6 +658,7 @@ bool IOLoginData::savePlayer(Player* player)
|
||||
|
||||
query << "`lastlogout` = " << player->getLastLogout() << ',';
|
||||
query << "`balance` = " << player->bankBalance << ',';
|
||||
query << "`stamina` = " << player->getStaminaMinutes() << ',';
|
||||
|
||||
query << "`skill_fist` = " << player->skills[SKILL_FIST].level << ',';
|
||||
query << "`skill_fist_tries` = " << player->skills[SKILL_FIST].tries << ',';
|
||||
|
@ -730,6 +730,8 @@ Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg)
|
||||
Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg)
|
||||
{
|
||||
Outfit_t outfit;
|
||||
outfit.lookAddons = getField<uint8_t>(L, arg, "lookAddons");
|
||||
|
||||
outfit.lookFeet = getField<uint8_t>(L, arg, "lookFeet");
|
||||
outfit.lookLegs = getField<uint8_t>(L, arg, "lookLegs");
|
||||
outfit.lookBody = getField<uint8_t>(L, arg, "lookBody");
|
||||
@ -882,6 +884,7 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit)
|
||||
setField(L, "lookBody", outfit.lookBody);
|
||||
setField(L, "lookLegs", outfit.lookLegs);
|
||||
setField(L, "lookFeet", outfit.lookFeet);
|
||||
setField(L, "lookAddons", outfit.lookAddons);
|
||||
}
|
||||
|
||||
#define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); }
|
||||
@ -1607,6 +1610,7 @@ void LuaScriptInterface::registerFunctions()
|
||||
registerEnumIn("configKeys", ConfigManager::ALLOW_CLONES)
|
||||
registerEnumIn("configKeys", ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)
|
||||
registerEnumIn("configKeys", ConfigManager::OPTIMIZE_DATABASE)
|
||||
registerEnumIn("configKeys", ConfigManager::STAMINA_SYSTEM)
|
||||
registerEnumIn("configKeys", ConfigManager::WARN_UNSAFE_SCRIPTS)
|
||||
registerEnumIn("configKeys", ConfigManager::CONVERT_UNSAFE_SCRIPTS)
|
||||
registerEnumIn("configKeys", ConfigManager::TELEPORT_NEWBIES)
|
||||
@ -2008,6 +2012,9 @@ void LuaScriptInterface::registerFunctions()
|
||||
registerMethod("Player", "getGroup", LuaScriptInterface::luaPlayerGetGroup);
|
||||
registerMethod("Player", "setGroup", LuaScriptInterface::luaPlayerSetGroup);
|
||||
|
||||
registerMethod("Player", "getStamina", LuaScriptInterface::luaPlayerGetStamina);
|
||||
registerMethod("Player", "setStamina", LuaScriptInterface::luaPlayerSetStamina);
|
||||
|
||||
registerMethod("Player", "getSoul", LuaScriptInterface::luaPlayerGetSoul);
|
||||
registerMethod("Player", "addSoul", LuaScriptInterface::luaPlayerAddSoul);
|
||||
registerMethod("Player", "getMaxSoul", LuaScriptInterface::luaPlayerGetMaxSoul);
|
||||
@ -7757,6 +7764,35 @@ int LuaScriptInterface::luaPlayerSetGroup(lua_State* L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaScriptInterface::luaPlayerGetStamina(lua_State* L)
|
||||
{
|
||||
// player:getStamina()
|
||||
Player* player = getUserdata<Player>(L, 1);
|
||||
if (player) {
|
||||
lua_pushnumber(L, player->getStaminaMinutes());
|
||||
}
|
||||
else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaScriptInterface::luaPlayerSetStamina(lua_State* L)
|
||||
{
|
||||
// player:setStamina(stamina)
|
||||
uint16_t stamina = getNumber<uint16_t>(L, 2);
|
||||
Player* player = getUserdata<Player>(L, 1);
|
||||
if (player) {
|
||||
player->staminaMinutes = std::min<uint16_t>(3360, stamina);
|
||||
player->sendStats();
|
||||
pushBoolean(L, true);
|
||||
}
|
||||
else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaScriptInterface::luaPlayerGetSoul(lua_State* L)
|
||||
{
|
||||
// player:getSoul()
|
||||
@ -10616,11 +10652,12 @@ int LuaScriptInterface::luaConditionSetSpeedDelta(lua_State* L)
|
||||
int LuaScriptInterface::luaConditionSetOutfit(lua_State* L)
|
||||
{
|
||||
// condition:setOutfit(outfit)
|
||||
// condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet)
|
||||
// condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons])
|
||||
Outfit_t outfit;
|
||||
if (isTable(L, 2)) {
|
||||
outfit = getOutfit(L, 2);
|
||||
} else {
|
||||
outfit.lookAddons = getNumber<uint8_t>(L, 8, outfit.lookAddons);
|
||||
outfit.lookFeet = getNumber<uint8_t>(L, 7);
|
||||
outfit.lookLegs = getNumber<uint8_t>(L, 6);
|
||||
outfit.lookBody = getNumber<uint8_t>(L, 5);
|
||||
|
@ -849,6 +849,9 @@ class LuaScriptInterface
|
||||
static int luaPlayerGetGroup(lua_State* L);
|
||||
static int luaPlayerSetGroup(lua_State* L);
|
||||
|
||||
static int luaPlayerGetStamina(lua_State* L);
|
||||
static int luaPlayerSetStamina(lua_State* L);
|
||||
|
||||
static int luaPlayerGetSoul(lua_State* L);
|
||||
static int luaPlayerAddSoul(lua_State* L);
|
||||
static int luaPlayerGetMaxSoul(lua_State* L);
|
||||
|
@ -759,6 +759,10 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa
|
||||
if ((attr = node.attribute("feet"))) {
|
||||
mType->info.outfit.lookFeet = pugi::cast<uint16_t>(attr.value());
|
||||
}
|
||||
|
||||
if ((attr = node.attribute("addons"))) {
|
||||
mType->info.outfit.lookAddons = pugi::cast<uint16_t>(attr.value());
|
||||
}
|
||||
} else if ((attr = node.attribute("typeex"))) {
|
||||
mType->info.outfit.lookTypeEx = pugi::cast<uint16_t>(attr.value());
|
||||
} else {
|
||||
|
@ -163,9 +163,13 @@ void mainLoader(int, char*[], ServiceManager* services)
|
||||
#endif
|
||||
|
||||
//set RSA key
|
||||
const char* p("14299623962416399520070177382898895550795403345466153217470516082934737582776038882967213386204600674145392845853859217990626450972452084065728686565928113");
|
||||
const char* q("7630979195970404721891201847792002125535401292779123937207447574596692788513647179235335529307251350570728407373705564708871762033017096809910315212884101");
|
||||
g_RSA.setKey(p, q);
|
||||
try {
|
||||
g_RSA.loadPEM("key.pem");
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
startupErrorMessage(e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << ">> Establishing database connection..." << std::flush;
|
||||
|
||||
@ -216,6 +220,13 @@ void mainLoader(int, char*[], ServiceManager* services)
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << ">> Loading outfits" << std::endl;
|
||||
auto& outfits = Outfits::getInstance();
|
||||
if (!outfits.loadFromXml()) {
|
||||
startupErrorMessage("Unable to load outfits!");
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << ">> Checking world type... " << std::flush;
|
||||
std::string worldType = asLowerCaseString(g_config.getString(ConfigManager::WORLD_TYPE));
|
||||
if (worldType == "pvp") {
|
||||
|
77
src/outfit.cpp
Normal file
77
src/outfit.cpp
Normal 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
63
src/outfit.h
Normal 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
|
@ -28,76 +28,76 @@ class Protocol;
|
||||
|
||||
class OutputMessage : public NetworkMessage
|
||||
{
|
||||
public:
|
||||
OutputMessage() = default;
|
||||
public:
|
||||
OutputMessage() = default;
|
||||
|
||||
// non-copyable
|
||||
OutputMessage(const OutputMessage&) = delete;
|
||||
OutputMessage& operator=(const OutputMessage&) = delete;
|
||||
// non-copyable
|
||||
OutputMessage(const OutputMessage&) = delete;
|
||||
OutputMessage& operator=(const OutputMessage&) = delete;
|
||||
|
||||
uint8_t* getOutputBuffer() {
|
||||
return buffer + outputBufferStart;
|
||||
}
|
||||
uint8_t* getOutputBuffer() {
|
||||
return buffer + outputBufferStart;
|
||||
}
|
||||
|
||||
void writeMessageLength() {
|
||||
add_header(info.length);
|
||||
}
|
||||
void writeMessageLength() {
|
||||
add_header(info.length);
|
||||
}
|
||||
|
||||
void addCryptoHeader() {
|
||||
writeMessageLength();
|
||||
}
|
||||
void addCryptoHeader() {
|
||||
writeMessageLength();
|
||||
}
|
||||
|
||||
inline void append(const NetworkMessage& msg) {
|
||||
auto msgLen = msg.getLength();
|
||||
memcpy(buffer + info.position, msg.getBuffer() + 4, msgLen);
|
||||
info.length += msgLen;
|
||||
info.position += msgLen;
|
||||
}
|
||||
inline void append(const NetworkMessage& msg) {
|
||||
auto msgLen = msg.getLength();
|
||||
memcpy(buffer + info.position, msg.getBuffer() + 4, msgLen);
|
||||
info.length += msgLen;
|
||||
info.position += msgLen;
|
||||
}
|
||||
|
||||
inline void append(const OutputMessage_ptr& msg) {
|
||||
auto msgLen = msg->getLength();
|
||||
memcpy(buffer + info.position, msg->getBuffer() + 4, msgLen);
|
||||
info.length += msgLen;
|
||||
info.position += msgLen;
|
||||
}
|
||||
inline void append(const OutputMessage_ptr& msg) {
|
||||
auto msgLen = msg->getLength();
|
||||
memcpy(buffer + info.position, msg->getBuffer() + 4, msgLen);
|
||||
info.length += msgLen;
|
||||
info.position += msgLen;
|
||||
}
|
||||
|
||||
protected:
|
||||
template <typename T>
|
||||
inline void add_header(T add) {
|
||||
assert(outputBufferStart >= sizeof(T));
|
||||
outputBufferStart -= sizeof(T);
|
||||
memcpy(buffer + outputBufferStart, &add, sizeof(T));
|
||||
//added header size to the message size
|
||||
info.length += sizeof(T);
|
||||
}
|
||||
protected:
|
||||
template <typename T>
|
||||
inline void add_header(T add) {
|
||||
assert(outputBufferStart >= sizeof(T));
|
||||
outputBufferStart -= sizeof(T);
|
||||
memcpy(buffer + outputBufferStart, &add, sizeof(T));
|
||||
//added header size to the message size
|
||||
info.length += sizeof(T);
|
||||
}
|
||||
|
||||
MsgSize_t outputBufferStart = INITIAL_BUFFER_POSITION;
|
||||
MsgSize_t outputBufferStart = INITIAL_BUFFER_POSITION;
|
||||
};
|
||||
|
||||
class OutputMessagePool
|
||||
{
|
||||
public:
|
||||
// non-copyable
|
||||
OutputMessagePool(const OutputMessagePool&) = delete;
|
||||
OutputMessagePool& operator=(const OutputMessagePool&) = delete;
|
||||
public:
|
||||
// non-copyable
|
||||
OutputMessagePool(const OutputMessagePool&) = delete;
|
||||
OutputMessagePool& operator=(const OutputMessagePool&) = delete;
|
||||
|
||||
static OutputMessagePool& getInstance() {
|
||||
static OutputMessagePool instance;
|
||||
return instance;
|
||||
}
|
||||
static OutputMessagePool& getInstance() {
|
||||
static OutputMessagePool instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void sendAll();
|
||||
void scheduleSendAll();
|
||||
void sendAll();
|
||||
void scheduleSendAll();
|
||||
|
||||
static OutputMessage_ptr getOutputMessage();
|
||||
static OutputMessage_ptr getOutputMessage();
|
||||
|
||||
void addProtocolToAutosend(Protocol_ptr protocol);
|
||||
void removeProtocolFromAutosend(const Protocol_ptr& protocol);
|
||||
private:
|
||||
OutputMessagePool() = default;
|
||||
//NOTE: A vector is used here because this container is mostly read
|
||||
//and relatively rarely modified (only when a client connects/disconnects)
|
||||
std::vector<Protocol_ptr> bufferedProtocols;
|
||||
void addProtocolToAutosend(Protocol_ptr protocol);
|
||||
void removeProtocolFromAutosend(const Protocol_ptr& protocol);
|
||||
private:
|
||||
OutputMessagePool() = default;
|
||||
//NOTE: A vector is used here because this container is mostly read
|
||||
//and relatively rarely modified (only when a client connects/disconnects)
|
||||
std::vector<Protocol_ptr> bufferedProtocols;
|
||||
};
|
||||
|
||||
|
||||
|
@ -473,3 +473,11 @@ void Party::clearPlayerPoints(Player* player)
|
||||
updateSharedExperience();
|
||||
}
|
||||
}
|
||||
|
||||
bool Party::canOpenCorpse(uint32_t ownerId) const
|
||||
{
|
||||
if (Player* player = g_game.getPlayerByID(ownerId)) {
|
||||
return leader->getID() == ownerId || player->getParty() == this;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ class Party
|
||||
bool empty() const {
|
||||
return memberList.empty() && inviteList.empty();
|
||||
}
|
||||
bool canOpenCorpse(uint32_t ownerId) const;
|
||||
|
||||
void shareExperience(uint64_t experience, Creature* source/* = nullptr*/);
|
||||
bool setSharedExperience(Player* player, bool sharedExpActive);
|
||||
|
123
src/player.cpp
123
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) {
|
||||
@ -504,6 +509,20 @@ uint16_t Player::getLookCorpse() const
|
||||
|
||||
void Player::addStorageValue(const uint32_t key, const int32_t value)
|
||||
{
|
||||
if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) {
|
||||
if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) {
|
||||
outfits.emplace_back(
|
||||
value >> 16,
|
||||
value & 0xFF
|
||||
);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
std::cout << "Warning: unknown reserved key: " << key << " player: " << getName() << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (value != -1) {
|
||||
storageMap[key] = value;
|
||||
} else {
|
||||
@ -3129,7 +3148,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 +3242,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 +3287,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;
|
||||
|
19
src/player.h
19
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 = 3360;
|
||||
uint16_t maxWriteLen = 0;
|
||||
|
||||
uint8_t soul = 0;
|
||||
|
@ -51,7 +51,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();
|
||||
}
|
||||
@ -71,7 +72,7 @@ void Protocol::XTEA_encrypt(OutputMessage& msg) const
|
||||
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]};
|
||||
const uint32_t k[] = { key[0], key[1], key[2], key[3] };
|
||||
while (readPos < messageLength) {
|
||||
uint32_t v0;
|
||||
memcpy(&v0, buffer + readPos, 4);
|
||||
@ -95,16 +96,16 @@ void Protocol::XTEA_encrypt(OutputMessage& msg) const
|
||||
|
||||
bool Protocol::XTEA_decrypt(NetworkMessage& msg) const
|
||||
{
|
||||
if (((msg.getLength() - 2) % 8) != 0) {
|
||||
if (((msg.getLength() - 2) & 7) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t delta = 0x61C88647;
|
||||
|
||||
uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition();
|
||||
const size_t messageLength = (msg.getLength() - 6);
|
||||
const size_t messageLength = (msg.getLength() - 2);
|
||||
size_t readPos = 0;
|
||||
const uint32_t k[] = {key[0], key[1], key[2], key[3]};
|
||||
const uint32_t k[] = { key[0], key[1], key[2], key[3] };
|
||||
while (readPos < messageLength) {
|
||||
uint32_t v0;
|
||||
memcpy(&v0, buffer + readPos, 4);
|
||||
@ -136,7 +137,7 @@ bool Protocol::XTEA_decrypt(NetworkMessage& msg) const
|
||||
|
||||
bool Protocol::RSA_decrypt(NetworkMessage& msg)
|
||||
{
|
||||
if ((msg.getLength() - msg.getBufferPosition()) < 128) {
|
||||
if ((msg.getLength() - msg.getBufferPosition()) != 128) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
118
src/protocol.h
118
src/protocol.h
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
|
||||
* Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
|
||||
* The Forgotten Server - a free and open-source MMORPG server emulator
|
||||
* Copyright (C) 2016 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
|
||||
@ -24,74 +24,74 @@
|
||||
|
||||
class Protocol : public std::enable_shared_from_this<Protocol>
|
||||
{
|
||||
public:
|
||||
explicit Protocol(Connection_ptr connection) : connection(connection) {}
|
||||
virtual ~Protocol() = default;
|
||||
public:
|
||||
explicit Protocol(Connection_ptr connection) : connection(connection), key(), encryptionEnabled(false), rawMessages(false) {}
|
||||
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(const uint32_t* key) {
|
||||
memcpy(this->key, key, sizeof(*key) * 4);
|
||||
}
|
||||
|
||||
uint32_t getIP() const;
|
||||
void XTEA_encrypt(OutputMessage& msg) const;
|
||||
bool XTEA_decrypt(NetworkMessage& msg) 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() {}
|
||||
friend class Connection;
|
||||
|
||||
void send(OutputMessage_ptr msg) const {
|
||||
if (auto connection = getConnection()) {
|
||||
connection->send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void XTEA_encrypt(OutputMessage& msg) const;
|
||||
bool XTEA_decrypt(NetworkMessage& msg) const;
|
||||
static bool RSA_decrypt(NetworkMessage& msg);
|
||||
|
||||
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;
|
||||
OutputMessage_ptr outputBuffer;
|
||||
private:
|
||||
const ConnectionWeak_ptr connection;
|
||||
uint32_t key[4];
|
||||
bool encryptionEnabled;
|
||||
bool rawMessages;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -302,25 +302,24 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
|
||||
disconnectClient("Account number or password is not correct.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Account account;
|
||||
if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) {
|
||||
disconnectClient("Account number or password is not correct.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Update premium days
|
||||
Game::updatePremium(account);
|
||||
|
||||
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);
|
||||
send(output);
|
||||
disconnect();
|
||||
|
||||
|
||||
}
|
||||
|
||||
void ProtocolGame::disconnectClient(const std::string& message) const
|
||||
@ -447,7 +446,8 @@ void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg)
|
||||
if (ground) {
|
||||
msg.addItem(ground);
|
||||
count = 1;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
@ -456,7 +456,11 @@ void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg)
|
||||
for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
|
||||
msg.addItem(*it);
|
||||
|
||||
if (++count == 10) {
|
||||
count++;
|
||||
if (count == 9 && tile->getPosition() == player->getPosition()) {
|
||||
break;
|
||||
}
|
||||
else if (count == 10) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -464,11 +468,20 @@ void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg)
|
||||
|
||||
const CreatureVector* creatures = tile->getCreatures();
|
||||
if (creatures) {
|
||||
bool playerAdded = false;
|
||||
for (const Creature* creature : boost::adaptors::reverse(*creatures)) {
|
||||
if (!player->canSeeCreature(creature)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tile->getPosition() == player->getPosition() && count == 9 && !playerAdded) {
|
||||
creature = player;
|
||||
}
|
||||
|
||||
if (creature->getID() == player->getID()) {
|
||||
playerAdded = true;
|
||||
}
|
||||
|
||||
bool known;
|
||||
uint32_t removedKnown;
|
||||
checkCreatureAsKnown(creature->getID(), known, removedKnown);
|
||||
@ -693,6 +706,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);
|
||||
}
|
||||
|
||||
@ -1154,11 +1168,12 @@ void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelNam
|
||||
writeToOutputBuffer(msg);
|
||||
}
|
||||
|
||||
|
||||
void ProtocolGame::sendIcons(uint16_t icons)
|
||||
{
|
||||
NetworkMessage msg;
|
||||
msg.addByte(0xA2);
|
||||
msg.addByte(static_cast<uint8_t>(icons));
|
||||
msg.add<uint16_t>(icons);
|
||||
writeToOutputBuffer(msg);
|
||||
}
|
||||
|
||||
@ -1190,6 +1205,8 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h
|
||||
writeToOutputBuffer(msg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack)
|
||||
{
|
||||
NetworkMessage msg;
|
||||
@ -1263,14 +1280,61 @@ void ProtocolGame::sendCreatureTurn(const Creature* creature, uint32_t stackPos)
|
||||
void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos/* = nullptr*/)
|
||||
{
|
||||
NetworkMessage msg;
|
||||
AddCreatureSpeak(msg, creature, type, text, 0, pos);
|
||||
msg.addByte(0xAA);
|
||||
|
||||
static uint32_t statementId = 0;
|
||||
msg.add<uint32_t>(++statementId);
|
||||
|
||||
msg.addString(creature->getName());
|
||||
|
||||
//Add level only for players
|
||||
if (const Player* speaker = creature->getPlayer()) {
|
||||
msg.add<uint16_t>(speaker->getLevel());
|
||||
}
|
||||
else {
|
||||
msg.add<uint16_t>(0x00);
|
||||
}
|
||||
|
||||
msg.addByte(type);
|
||||
if (pos) {
|
||||
msg.addPosition(*pos);
|
||||
}
|
||||
else {
|
||||
msg.addPosition(creature->getPosition());
|
||||
}
|
||||
|
||||
msg.addString(text);
|
||||
writeToOutputBuffer(msg);
|
||||
}
|
||||
|
||||
void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId)
|
||||
{
|
||||
NetworkMessage msg;
|
||||
AddCreatureSpeak(msg, creature, type, text, channelId);
|
||||
msg.addByte(0xAA);
|
||||
|
||||
static uint32_t statementId = 0;
|
||||
msg.add<uint32_t>(++statementId);
|
||||
if (!creature) {
|
||||
msg.add<uint32_t>(0x00);
|
||||
}
|
||||
else if (type == TALKTYPE_CHANNEL_R2) {
|
||||
msg.add<uint32_t>(0x00);
|
||||
type = TALKTYPE_CHANNEL_R1;
|
||||
}
|
||||
else {
|
||||
msg.addString(creature->getName());
|
||||
//Add level only for players
|
||||
if (const Player* speaker = creature->getPlayer()) {
|
||||
msg.add<uint16_t>(speaker->getLevel());
|
||||
}
|
||||
else {
|
||||
msg.add<uint16_t>(0x00);
|
||||
}
|
||||
}
|
||||
|
||||
msg.addByte(type);
|
||||
msg.add<uint16_t>(channelId);
|
||||
msg.addString(text);
|
||||
writeToOutputBuffer(msg);
|
||||
}
|
||||
|
||||
@ -1280,16 +1344,13 @@ void ProtocolGame::sendPrivateMessage(const Player* speaker, SpeakClasses type,
|
||||
msg.addByte(0xAA);
|
||||
static uint32_t statementId = 0;
|
||||
msg.add<uint32_t>(++statementId);
|
||||
if (type == TALKTYPE_RVR_ANSWER) {
|
||||
msg.addString("Gamemaster");
|
||||
} else {
|
||||
if (speaker) {
|
||||
msg.addString(speaker->getName());
|
||||
} else {
|
||||
msg.add<uint32_t>(0x00);
|
||||
}
|
||||
if (speaker) {
|
||||
msg.addString(speaker->getName());
|
||||
msg.add<uint16_t>(speaker->getLevel());
|
||||
}
|
||||
else {
|
||||
msg.add<uint32_t>(0x00);
|
||||
}
|
||||
|
||||
msg.addByte(type);
|
||||
msg.addString(text);
|
||||
writeToOutputBuffer(msg);
|
||||
@ -1493,7 +1554,8 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos
|
||||
msg.addByte(0x0A);
|
||||
|
||||
msg.add<uint32_t>(player->getID());
|
||||
msg.add<uint16_t>(0x32); // beat duration (50)
|
||||
msg.addByte(0x32); // beat duration (50)
|
||||
msg.addByte(0x00);
|
||||
|
||||
// can report bugs?
|
||||
if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) {
|
||||
@ -1517,16 +1579,9 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos
|
||||
sendMagicEffect(pos, CONST_ME_TELEPORT);
|
||||
}
|
||||
|
||||
sendInventoryItem(CONST_SLOT_HEAD, player->getInventoryItem(CONST_SLOT_HEAD));
|
||||
sendInventoryItem(CONST_SLOT_NECKLACE, player->getInventoryItem(CONST_SLOT_NECKLACE));
|
||||
sendInventoryItem(CONST_SLOT_BACKPACK, player->getInventoryItem(CONST_SLOT_BACKPACK));
|
||||
sendInventoryItem(CONST_SLOT_ARMOR, player->getInventoryItem(CONST_SLOT_ARMOR));
|
||||
sendInventoryItem(CONST_SLOT_RIGHT, player->getInventoryItem(CONST_SLOT_RIGHT));
|
||||
sendInventoryItem(CONST_SLOT_LEFT, player->getInventoryItem(CONST_SLOT_LEFT));
|
||||
sendInventoryItem(CONST_SLOT_LEGS, player->getInventoryItem(CONST_SLOT_LEGS));
|
||||
sendInventoryItem(CONST_SLOT_FEET, player->getInventoryItem(CONST_SLOT_FEET));
|
||||
sendInventoryItem(CONST_SLOT_RING, player->getInventoryItem(CONST_SLOT_RING));
|
||||
sendInventoryItem(CONST_SLOT_AMMO, player->getInventoryItem(CONST_SLOT_AMMO));
|
||||
for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
|
||||
sendInventoryItem(static_cast<slots_t>(i), player->getInventoryItem(static_cast<slots_t>(i)));
|
||||
}
|
||||
|
||||
sendStats();
|
||||
sendSkills();
|
||||
@ -1715,20 +1770,30 @@ void ProtocolGame::sendOutfitWindow()
|
||||
Outfit_t currentOutfit = player->getDefaultOutfit();
|
||||
AddOutfit(msg, currentOutfit);
|
||||
|
||||
if (player->getSex() == PLAYERSEX_MALE) {
|
||||
msg.add<uint16_t>(128);
|
||||
if (player->isPremium()) {
|
||||
msg.add<uint16_t>(134);
|
||||
} else {
|
||||
msg.add<uint16_t>(131);
|
||||
}
|
||||
} else {
|
||||
msg.add<uint16_t>(136);
|
||||
if (player->isPremium()) {
|
||||
msg.add<uint16_t>(142);
|
||||
} else {
|
||||
msg.add<uint16_t>(139);
|
||||
std::vector<ProtocolOutfit> 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() == 15) { // Game client doesn't allow more than 15 outfits
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
msg.addByte(protocolOutfits.size());
|
||||
for (const ProtocolOutfit& outfit : protocolOutfits) {
|
||||
msg.add<uint16_t>(outfit.lookType);
|
||||
msg.addByte(outfit.addons);
|
||||
}
|
||||
|
||||
writeToOutputBuffer(msg);
|
||||
@ -1810,14 +1875,14 @@ 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->getMaxHealth(), std::numeric_limits<uint16_t>::max()));
|
||||
msg.add<uint16_t>(static_cast<uint16_t>(player->getFreeCapacity() / 100.));
|
||||
if (player->getExperience() >= std::numeric_limits<uint32_t>::max()) {
|
||||
msg.add<uint32_t>(0);
|
||||
} else {
|
||||
msg.add<uint32_t>(static_cast<uint32_t>(player->getExperience()));
|
||||
}
|
||||
msg.add<uint16_t>(static_cast<uint16_t>(player->getLevel()));
|
||||
|
||||
msg.add<uint16_t>(player->getFreeCapacity() / 100);
|
||||
|
||||
msg.add<uint32_t>(std::min<uint32_t>(player->getExperience(), 0x7FFFFFFF));
|
||||
|
||||
msg.add<uint16_t>(player->getLevel());
|
||||
msg.addByte(player->getLevelPercent());
|
||||
|
||||
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()));
|
||||
|
||||
@ -1825,6 +1890,8 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
|
||||
msg.addByte(player->getMagicLevelPercent());
|
||||
|
||||
msg.addByte(player->getSoul());
|
||||
|
||||
msg.add<uint16_t>(player->getStaminaMinutes());
|
||||
}
|
||||
|
||||
void ProtocolGame::AddPlayerSkills(NetworkMessage& msg)
|
||||
@ -1846,6 +1913,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);
|
||||
}
|
||||
@ -1869,53 +1937,6 @@ void ProtocolGame::AddCreatureLight(NetworkMessage& msg, const Creature* creatur
|
||||
msg.addByte(lightInfo.color);
|
||||
}
|
||||
|
||||
void ProtocolGame::AddCreatureSpeak(NetworkMessage& msg, const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId, const Position* pos /*= nullptr*/)
|
||||
{
|
||||
msg.addByte(0xAA);
|
||||
static uint32_t statementId = 0;
|
||||
msg.add<uint32_t>(++statementId);
|
||||
|
||||
if (type != TALKTYPE_RVR_ANSWER) {
|
||||
if (type != TALKTYPE_CHANNEL_R2) {
|
||||
if (creature) {
|
||||
msg.addString(creature->getName());
|
||||
} else {
|
||||
msg.add<uint16_t>(0);
|
||||
}
|
||||
} else {
|
||||
msg.add<uint16_t>(0);
|
||||
}
|
||||
}
|
||||
|
||||
msg.addByte(type);
|
||||
switch (type) {
|
||||
case TALKTYPE_SAY:
|
||||
case TALKTYPE_WHISPER:
|
||||
case TALKTYPE_YELL:
|
||||
case TALKTYPE_MONSTER_SAY:
|
||||
case TALKTYPE_MONSTER_YELL:
|
||||
if (!pos) {
|
||||
msg.addPosition(creature->getPosition());
|
||||
} else {
|
||||
msg.addPosition(*pos);
|
||||
}
|
||||
break;
|
||||
case TALKTYPE_CHANNEL_Y:
|
||||
case TALKTYPE_CHANNEL_R1:
|
||||
case TALKTYPE_CHANNEL_R2:
|
||||
case TALKTYPE_CHANNEL_O:
|
||||
msg.add<uint16_t>(channelId);
|
||||
break;
|
||||
case TALKTYPE_RVR_CHANNEL:
|
||||
msg.add<uint32_t>(0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
msg.addString(text);
|
||||
}
|
||||
|
||||
//tile
|
||||
void ProtocolGame::RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos)
|
||||
{
|
||||
|
@ -50,8 +50,8 @@ 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
|
||||
|
||||
static const char* protocol_name() {
|
||||
return "gameworld protocol";
|
||||
@ -71,7 +71,6 @@ class ProtocolGame final : public Protocol
|
||||
return std::static_pointer_cast<ProtocolGame>(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 +85,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);
|
||||
@ -234,7 +234,6 @@ class ProtocolGame final : public Protocol
|
||||
void AddPlayerSkills(NetworkMessage& msg);
|
||||
void AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo);
|
||||
void AddCreatureLight(NetworkMessage& msg, const Creature* creature);
|
||||
void AddCreatureSpeak(NetworkMessage& msg, const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId, const Position* pos = nullptr);
|
||||
|
||||
//tiles
|
||||
static void RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos);
|
||||
@ -262,8 +261,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;
|
||||
};
|
||||
|
@ -32,32 +32,22 @@
|
||||
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, 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;
|
||||
}
|
||||
|
||||
@ -91,7 +81,8 @@ void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string&
|
||||
//Add premium days
|
||||
if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) {
|
||||
output->add<uint16_t>(0xFFFF);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
output->add<uint16_t>(account.premiumDays);
|
||||
}
|
||||
|
||||
@ -100,6 +91,7 @@ void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string&
|
||||
disconnect();
|
||||
}
|
||||
|
||||
|
||||
void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
|
||||
{
|
||||
if (g_game.getGameState() == GAME_STATE_SHUTDOWN) {
|
||||
@ -109,15 +101,28 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
|
||||
|
||||
msg.skipBytes(2); // client OS
|
||||
|
||||
/*uint16_t version =*/ msg.get<uint16_t>();
|
||||
msg.skipBytes(12);
|
||||
uint16_t version = msg.get<uint16_t>();
|
||||
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;
|
||||
@ -131,19 +136,20 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
|
||||
enableXTEAEncryption();
|
||||
setXTEAKey(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,22 +166,22 @@ 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;
|
||||
}
|
||||
|
||||
uint32_t accountNumber = msg.get<uint32_t>();
|
||||
if (!accountNumber) {
|
||||
disconnectClient("Invalid account number.");
|
||||
disconnectClient("Invalid account number.", version);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string password = msg.getString();
|
||||
if (password.empty()) {
|
||||
disconnectClient("Invalid password.");
|
||||
disconnectClient("Invalid password.", version);
|
||||
return;
|
||||
}
|
||||
|
||||
auto thisPtr = std::static_pointer_cast<ProtocolLogin>(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)));
|
||||
}
|
||||
|
@ -31,20 +31,18 @@ class ProtocolLogin : public Protocol
|
||||
// static protocol information
|
||||
enum {server_sends_first = false};
|
||||
enum {protocol_identifier = 0x01};
|
||||
enum {use_checksum = true};
|
||||
static const char* protocol_name() {
|
||||
return "login 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, uint16_t version);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -29,7 +29,6 @@ class ProtocolStatus final : public Protocol
|
||||
// static protocol information
|
||||
enum {server_sends_first = false};
|
||||
enum {protocol_identifier = 0xFF};
|
||||
enum {use_checksum = false};
|
||||
static const char* protocol_name() {
|
||||
return "status protocol";
|
||||
}
|
||||
|
116
src/rsa.cpp
116
src/rsa.cpp
@ -21,72 +21,60 @@
|
||||
|
||||
#include "rsa.h"
|
||||
|
||||
RSA::RSA()
|
||||
#include <cryptopp/base64.h>
|
||||
#include <cryptopp/osrng.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
static CryptoPP::AutoSeededRandomPool prng;
|
||||
|
||||
void RSA::decrypt(char* msg) const
|
||||
{
|
||||
mpz_init(n);
|
||||
mpz_init2(d, 1024);
|
||||
CryptoPP::Integer m{ reinterpret_cast<uint8_t*>(msg), 128 };
|
||||
auto c = pk.CalculateInverse(prng, m);
|
||||
c.Encode(reinterpret_cast<uint8_t*>(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<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';
|
||||
}
|
||||
}
|
||||
|
26
src/rsa.h
26
src/rsa.h
@ -20,24 +20,24 @@
|
||||
#ifndef FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91
|
||||
#define FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91
|
||||
|
||||
#include <gmp.h>
|
||||
#include <cryptopp/rsa.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
@ -56,7 +56,8 @@ void ServiceManager::stop()
|
||||
for (auto& servicePortIt : acceptors) {
|
||||
try {
|
||||
io_service.post(std::bind(&ServicePort::onStopServer, servicePortIt.second));
|
||||
} catch (boost::system::system_error& e) {
|
||||
}
|
||||
catch (boost::system::system_error& e) {
|
||||
std::cout << "[ServiceManager::stop] Network Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@ -114,20 +115,23 @@ void ServicePort::onAccept(Connection_ptr connection, const boost::system::error
|
||||
Service_ptr service = services.front();
|
||||
if (service->is_single_socket()) {
|
||||
connection->accept(service->make_protocol(connection));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
connection->accept();
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
connection->close(Connection::FORCE_CLOSE);
|
||||
}
|
||||
|
||||
accept();
|
||||
} else if (error != boost::asio::error::operation_aborted) {
|
||||
}
|
||||
else if (error != boost::asio::error::operation_aborted) {
|
||||
if (!pendingStart) {
|
||||
close();
|
||||
pendingStart = true;
|
||||
g_scheduler.addEvent(createSchedulerTask(15000,
|
||||
std::bind(&ServicePort::openAcceptor, std::weak_ptr<ServicePort>(shared_from_this()), serverPort)));
|
||||
std::bind(&ServicePort::openAcceptor, std::weak_ptr<ServicePort>(shared_from_this()), serverPort)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,21 +171,23 @@ void ServicePort::open(uint16_t port)
|
||||
try {
|
||||
if (g_config.getBoolean(ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)) {
|
||||
acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint(
|
||||
boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort)));
|
||||
} else {
|
||||
boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort)));
|
||||
}
|
||||
else {
|
||||
acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint(
|
||||
boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort)));
|
||||
boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort)));
|
||||
}
|
||||
|
||||
acceptor->set_option(boost::asio::ip::tcp::no_delay(true));
|
||||
|
||||
accept();
|
||||
} catch (boost::system::system_error& e) {
|
||||
}
|
||||
catch (boost::system::system_error& e) {
|
||||
std::cout << "[ServicePort::open] Error: " << e.what() << std::endl;
|
||||
|
||||
pendingStart = true;
|
||||
g_scheduler.addEvent(createSchedulerTask(15000,
|
||||
std::bind(&ServicePort::openAcceptor, std::weak_ptr<ServicePort>(shared_from_this()), port)));
|
||||
std::bind(&ServicePort::openAcceptor, std::weak_ptr<ServicePort>(shared_from_this()), port)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,7 +201,7 @@ void ServicePort::close()
|
||||
|
||||
bool ServicePort::add_service(const Service_ptr& new_svc)
|
||||
{
|
||||
if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket();})) {
|
||||
if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket(); })) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
125
src/server.h
125
src/server.h
@ -27,94 +27,94 @@ class Protocol;
|
||||
|
||||
class ServiceBase
|
||||
{
|
||||
public:
|
||||
virtual bool is_single_socket() const = 0;
|
||||
virtual uint8_t get_protocol_identifier() const = 0;
|
||||
virtual const char* get_protocol_name() const = 0;
|
||||
public:
|
||||
virtual bool is_single_socket() const = 0;
|
||||
virtual uint8_t get_protocol_identifier() const = 0;
|
||||
virtual const char* get_protocol_name() const = 0;
|
||||
|
||||
virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0;
|
||||
virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0;
|
||||
};
|
||||
|
||||
template <typename ProtocolType>
|
||||
class Service final : public ServiceBase
|
||||
{
|
||||
public:
|
||||
bool is_single_socket() const final {
|
||||
return ProtocolType::server_sends_first;
|
||||
}
|
||||
uint8_t get_protocol_identifier() const final {
|
||||
return ProtocolType::protocol_identifier;
|
||||
}
|
||||
const char* get_protocol_name() const final {
|
||||
return ProtocolType::protocol_name();
|
||||
}
|
||||
public:
|
||||
bool is_single_socket() const final {
|
||||
return ProtocolType::server_sends_first;
|
||||
}
|
||||
uint8_t get_protocol_identifier() const final {
|
||||
return ProtocolType::protocol_identifier;
|
||||
}
|
||||
const char* get_protocol_name() const final {
|
||||
return ProtocolType::protocol_name();
|
||||
}
|
||||
|
||||
Protocol_ptr make_protocol(const Connection_ptr& c) const final {
|
||||
return std::make_shared<ProtocolType>(c);
|
||||
}
|
||||
Protocol_ptr make_protocol(const Connection_ptr& c) const final {
|
||||
return std::make_shared<ProtocolType>(c);
|
||||
}
|
||||
};
|
||||
|
||||
class ServicePort : public std::enable_shared_from_this<ServicePort>
|
||||
{
|
||||
public:
|
||||
explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {}
|
||||
~ServicePort();
|
||||
public:
|
||||
explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {}
|
||||
~ServicePort();
|
||||
|
||||
// non-copyable
|
||||
ServicePort(const ServicePort&) = delete;
|
||||
ServicePort& operator=(const ServicePort&) = delete;
|
||||
// non-copyable
|
||||
ServicePort(const ServicePort&) = delete;
|
||||
ServicePort& operator=(const ServicePort&) = delete;
|
||||
|
||||
static void openAcceptor(std::weak_ptr<ServicePort> weak_service, uint16_t port);
|
||||
void open(uint16_t port);
|
||||
void close();
|
||||
bool is_single_socket() const;
|
||||
std::string get_protocol_names() const;
|
||||
static void openAcceptor(std::weak_ptr<ServicePort> weak_service, uint16_t port);
|
||||
void open(uint16_t port);
|
||||
void close();
|
||||
bool is_single_socket() const;
|
||||
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;
|
||||
bool add_service(const Service_ptr& new_svc);
|
||||
Protocol_ptr make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const;
|
||||
|
||||
void onStopServer();
|
||||
void onAccept(Connection_ptr connection, const boost::system::error_code& error);
|
||||
void onStopServer();
|
||||
void onAccept(Connection_ptr connection, const boost::system::error_code& error);
|
||||
|
||||
protected:
|
||||
void accept();
|
||||
protected:
|
||||
void accept();
|
||||
|
||||
boost::asio::io_service& io_service;
|
||||
std::unique_ptr<boost::asio::ip::tcp::acceptor> acceptor;
|
||||
std::vector<Service_ptr> services;
|
||||
boost::asio::io_service& io_service;
|
||||
std::unique_ptr<boost::asio::ip::tcp::acceptor> acceptor;
|
||||
std::vector<Service_ptr> services;
|
||||
|
||||
uint16_t serverPort = 0;
|
||||
bool pendingStart = false;
|
||||
uint16_t serverPort = 0;
|
||||
bool pendingStart = false;
|
||||
};
|
||||
|
||||
class ServiceManager
|
||||
{
|
||||
public:
|
||||
ServiceManager() = default;
|
||||
~ServiceManager();
|
||||
public:
|
||||
ServiceManager() = default;
|
||||
~ServiceManager();
|
||||
|
||||
// non-copyable
|
||||
ServiceManager(const ServiceManager&) = delete;
|
||||
ServiceManager& operator=(const ServiceManager&) = delete;
|
||||
// non-copyable
|
||||
ServiceManager(const ServiceManager&) = delete;
|
||||
ServiceManager& operator=(const ServiceManager&) = delete;
|
||||
|
||||
void run();
|
||||
void stop();
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
template <typename ProtocolType>
|
||||
bool add(uint16_t port);
|
||||
template <typename ProtocolType>
|
||||
bool add(uint16_t port);
|
||||
|
||||
bool is_running() const {
|
||||
return acceptors.empty() == false;
|
||||
}
|
||||
bool is_running() const {
|
||||
return acceptors.empty() == false;
|
||||
}
|
||||
|
||||
protected:
|
||||
void die();
|
||||
protected:
|
||||
void die();
|
||||
|
||||
std::unordered_map<uint16_t, ServicePort_ptr> acceptors;
|
||||
std::unordered_map<uint16_t, ServicePort_ptr> acceptors;
|
||||
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::deadline_timer death_timer { io_service };
|
||||
bool running = false;
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::deadline_timer death_timer{ io_service };
|
||||
bool running = false;
|
||||
};
|
||||
|
||||
template <typename ProtocolType>
|
||||
@ -133,13 +133,14 @@ bool ServiceManager::add(uint16_t port)
|
||||
service_port = std::make_shared<ServicePort>(io_service);
|
||||
service_port->open(port);
|
||||
acceptors[port] = service_port;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
service_port = foundServicePort->second;
|
||||
|
||||
if (service_port->is_single_socket() || ProtocolType::server_sends_first) {
|
||||
std::cout << "ERROR: " << ProtocolType::protocol_name() <<
|
||||
" and " << service_port->get_protocol_names() <<
|
||||
" cannot use the same port " << port << '.' << std::endl;
|
||||
" and " << service_port->get_protocol_names() <<
|
||||
" cannot use the same port " << port << '.' << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1801,47 +1801,46 @@ ReturnValue RuneSpell::canExecuteAction(const Player* player, const Position& to
|
||||
return RETURNVALUE_NOERROR;
|
||||
}
|
||||
|
||||
bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition)
|
||||
bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition, bool isHotkey)
|
||||
{
|
||||
if (!playerRuneSpellCheck(player, toPosition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
if (scripted) {
|
||||
LuaVariant var;
|
||||
|
||||
if (needTarget) {
|
||||
var.type = VARIANT_NUMBER;
|
||||
|
||||
if (target == nullptr) {
|
||||
Tile* toTile = g_game.map.getTile(toPosition);
|
||||
if (toTile) {
|
||||
const Creature* visibleCreature = toTile->getTopCreature();
|
||||
if (visibleCreature) {
|
||||
var.number = visibleCreature->getID();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var.number = target->getCreature()->getID();
|
||||
}
|
||||
} else {
|
||||
var.type = VARIANT_POSITION;
|
||||
var.pos = toPosition;
|
||||
}
|
||||
|
||||
result = internalCastSpell(player, var);
|
||||
} else if (runeFunction) {
|
||||
result = runeFunction(this, player, toPosition);
|
||||
if (!scripted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
LuaVariant var;
|
||||
|
||||
if (needTarget) {
|
||||
var.type = VARIANT_NUMBER;
|
||||
|
||||
if (target == nullptr) {
|
||||
Tile* toTile = g_game.map.getTile(toPosition);
|
||||
if (toTile) {
|
||||
const Creature* visibleCreature = toTile->getBottomVisibleCreature(player);
|
||||
if (visibleCreature) {
|
||||
var.number = visibleCreature->getID();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var.number = target->getCreature()->getID();
|
||||
}
|
||||
}
|
||||
else {
|
||||
var.type = VARIANT_POSITION;
|
||||
var.pos = toPosition;
|
||||
}
|
||||
|
||||
if (!internalCastSpell(player, var, isHotkey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
postCastSpell(player);
|
||||
if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES)) {
|
||||
int32_t newCount = std::max<int32_t>(0, item->getCharges() - 1);
|
||||
int32_t newCount = std::max<int32_t>(0, item->getItemCount() - 1);
|
||||
g_game.transformItem(item, item->getID(), newCount);
|
||||
}
|
||||
return true;
|
||||
@ -1852,7 +1851,7 @@ bool RuneSpell::castSpell(Creature* creature)
|
||||
LuaVariant var;
|
||||
var.type = VARIANT_NUMBER;
|
||||
var.number = creature->getID();
|
||||
return internalCastSpell(creature, var);
|
||||
return internalCastSpell(creature, var, false);
|
||||
}
|
||||
|
||||
bool RuneSpell::castSpell(Creature* creature, Creature* target)
|
||||
@ -1860,23 +1859,24 @@ bool RuneSpell::castSpell(Creature* creature, Creature* target)
|
||||
LuaVariant var;
|
||||
var.type = VARIANT_NUMBER;
|
||||
var.number = target->getID();
|
||||
return internalCastSpell(creature, var);
|
||||
return internalCastSpell(creature, var, false);
|
||||
}
|
||||
|
||||
bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var)
|
||||
bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey)
|
||||
{
|
||||
bool result;
|
||||
if (scripted) {
|
||||
result = executeCastSpell(creature, var);
|
||||
} else {
|
||||
result = executeCastSpell(creature, var, isHotkey);
|
||||
}
|
||||
else {
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var)
|
||||
bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey)
|
||||
{
|
||||
//onCastSpell(creature, var)
|
||||
//onCastSpell(creature, var, isHotkey)
|
||||
if (!scriptInterface->reserveScriptEnv()) {
|
||||
std::cout << "[Error - RuneSpell::executeCastSpell] Call stack overflow" << std::endl;
|
||||
return false;
|
||||
@ -1894,5 +1894,7 @@ bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var)
|
||||
|
||||
LuaScriptInterface::pushVariant(L, var);
|
||||
|
||||
return scriptInterface->callFunction(2);
|
||||
LuaScriptInterface::pushBoolean(L, isHotkey);
|
||||
|
||||
return scriptInterface->callFunction(3);
|
||||
}
|
||||
|
18
src/spells.h
18
src/spells.h
@ -56,7 +56,7 @@ class Spells final : public BaseEvents
|
||||
TalkActionResult_t playerSaySpell(Player* player, std::string& words);
|
||||
|
||||
static Position getCasterPosition(Creature* creature, Direction dir);
|
||||
std::string getScriptBaseName() const final;
|
||||
std::string getScriptBaseName() const override;
|
||||
|
||||
protected:
|
||||
void clear() final;
|
||||
@ -94,9 +94,9 @@ class CombatSpell final : public Event, public BaseSpell
|
||||
CombatSpell(const CombatSpell&) = delete;
|
||||
CombatSpell& operator=(const CombatSpell&) = delete;
|
||||
|
||||
bool castSpell(Creature* creature) final;
|
||||
bool castSpell(Creature* creature, Creature* target) final;
|
||||
bool configureEvent(const pugi::xml_node&) final {
|
||||
bool castSpell(Creature* creature) override;
|
||||
bool castSpell(Creature* creature, Creature* target) override;
|
||||
bool configureEvent(const pugi::xml_node&) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ class CombatSpell final : public Event, public BaseSpell
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string getScriptEventName() const final {
|
||||
std::string getScriptEventName() const override {
|
||||
return "onCastSpell";
|
||||
}
|
||||
|
||||
@ -209,7 +209,7 @@ class InstantSpell : public TalkAction, public Spell
|
||||
//scripting
|
||||
bool executeCastSpell(Creature* creature, const LuaVariant& var);
|
||||
|
||||
bool isInstant() const final {
|
||||
bool isInstant() const override {
|
||||
return true;
|
||||
}
|
||||
bool getHasParam() const {
|
||||
@ -291,13 +291,13 @@ class RuneSpell final : public Action, public Spell
|
||||
return targetCreature;
|
||||
}
|
||||
|
||||
bool executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition) final;
|
||||
bool executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey) override;
|
||||
|
||||
bool castSpell(Creature* creature) final;
|
||||
bool castSpell(Creature* creature, Creature* target) final;
|
||||
|
||||
//scripting
|
||||
bool executeCastSpell(Creature* creature, const LuaVariant& var);
|
||||
bool executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey);
|
||||
|
||||
bool isInstant() const final {
|
||||
return false;
|
||||
@ -312,7 +312,7 @@ class RuneSpell final : public Action, public Spell
|
||||
static RuneSpellFunction Illusion;
|
||||
static RuneSpellFunction Convince;
|
||||
|
||||
bool internalCastSpell(Creature* creature, const LuaVariant& var);
|
||||
bool internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey);
|
||||
|
||||
RuneSpellFunction* runeFunction = nullptr;
|
||||
uint16_t runeId = 0;
|
||||
|
@ -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<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)
|
||||
{
|
||||
size_t pos = 0;
|
||||
@ -790,6 +835,7 @@ std::string getSkillName(uint8_t skillid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string ucfirst(std::string str)
|
||||
{
|
||||
for (char& i : str) {
|
||||
|
@ -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);
|
||||
|
140
src/xtea.cpp
Normal file
140
src/xtea.cpp
Normal 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
32
src/xtea.h
Normal 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
|
@ -190,6 +190,7 @@
|
||||
<PrecompiledHeaderFile>otpch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\otserv.cpp" />
|
||||
<ClCompile Include="..\src\outfit.cpp" />
|
||||
<ClCompile Include="..\src\outputmessage.cpp" />
|
||||
<ClCompile Include="..\src\party.cpp" />
|
||||
<ClCompile Include="..\src\player.cpp" />
|
||||
@ -215,6 +216,7 @@
|
||||
<ClCompile Include="..\src\vocation.cpp" />
|
||||
<ClCompile Include="..\src\waitlist.cpp" />
|
||||
<ClCompile Include="..\src\wildcardtree.cpp" />
|
||||
<ClCompile Include="..\src\xtea.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\src\account.h" />
|
||||
@ -263,6 +265,7 @@
|
||||
<ClInclude Include="..\src\networkmessage.h" />
|
||||
<ClInclude Include="..\src\npc.h" />
|
||||
<ClInclude Include="..\src\otpch.h" />
|
||||
<ClInclude Include="..\src\outfit.h" />
|
||||
<ClInclude Include="..\src\outputmessage.h" />
|
||||
<ClInclude Include="..\src\party.h" />
|
||||
<ClInclude Include="..\src\player.h" />
|
||||
@ -291,6 +294,7 @@
|
||||
<ClInclude Include="..\src\vocation.h" />
|
||||
<ClInclude Include="..\src\waitlist.h" />
|
||||
<ClInclude Include="..\src\wildcardtree.h" />
|
||||
<ClInclude Include="..\src\xtea.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
Loading…
x
Reference in New Issue
Block a user