fix: security vulnerabilities (#1980)

* fix: security vulnerabilities

Tested that all functions related to the touched files work

will test sqlite on a CI build

* fix failing test

* ai feedback

* add buffer size checking

* use c_str

* dont log session key

* Try this for a mac definition

* be quiet apple
This commit is contained in:
David Markowitz
2026-06-07 20:59:11 -07:00
committed by GitHub
parent f6c9a27a2b
commit a156a8fcba
109 changed files with 806 additions and 514 deletions

View File

@@ -112,6 +112,7 @@ public:
uint16_t GetNetworkId() const;
// Cannot return nullptr.
Entity* GetOwner() const;
const NiPoint3& GetDefaultPosition() const;

View File

@@ -30,6 +30,21 @@ void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream& bitS
return;
}
// So a player can't send an arbitrary behaviorID in a modified client and cast any behavior on any air behavior
Behavior* toSync = nullptr;
if (m_GroundAction->GetBehaviorID() == behaviorId) {
toSync = m_GroundAction;
} else if (m_HitAction->GetBehaviorID() == behaviorId) {
toSync = m_HitAction;
} else if (m_HitActionEnemy->GetBehaviorID() == behaviorId) {
toSync = m_HitActionEnemy;
} else if (m_TimeoutAction->GetBehaviorID() == behaviorId) {
toSync = m_TimeoutAction;
} else {
LOG("Invalid Air Movement Behavior sync for behaviorID %i on behavior %i", behaviorId, m_behaviorId);
return;
}
LWOOBJID target{};
if (!bitStream.Read(target)) {
@@ -37,15 +52,17 @@ void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream& bitS
return;
}
auto* behavior = CreateBehavior(behaviorId);
if (Game::entityManager->GetEntity(target) != nullptr) {
branch.target = target;
}
behavior->Handle(context, bitStream, branch);
toSync->Handle(context, bitStream, branch);
}
void AirMovementBehavior::Load() {
this->m_Timeout = (GetFloat("timeout_ms") / 1000.0f);
m_Timeout = (GetFloat("timeout_ms") / 1000.0f);
m_GroundAction = GetAction("ground_action");
m_HitAction = GetAction("hit_action");
m_HitActionEnemy = GetAction("hit_action_enemy");
m_TimeoutAction = GetAction("timeout_action");
}

View File

@@ -15,4 +15,9 @@ public:
void Load() override;
private:
float m_Timeout;
Behavior* m_GroundAction{};
Behavior* m_HitAction{};
Behavior* m_HitActionEnemy{};
Behavior* m_TimeoutAction{};
};

View File

@@ -42,6 +42,7 @@ void AreaOfEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream& b
LWOOBJID target{};
if (!bitStream.Read(target)) {
LOG("failed to read in target %i from bitStream, aborting target Handle!", i);
continue;
};
targets.push_back(target);
}

View File

@@ -68,7 +68,7 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
}
if (isBlocked) {
destroyableComponent->SetAttacksToBlock(std::min(destroyableComponent->GetAttacksToBlock() - 1, 0U));
destroyableComponent->SetAttacksToBlock(std::max<int32_t>(static_cast<int32_t>(destroyableComponent->GetAttacksToBlock() - 1), 0));
Game::entityManager->SerializeEntity(targetEntity);
this->m_OnFailBlocked->Handle(context, bitStream, branch);
return;
@@ -103,9 +103,10 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
return;
}
uint32_t totalDamageDealt = armorDamageDealt + healthDamageDealt;
uint64_t totalDamageDealt = armorDamageDealt + healthDamageDealt;
// A value that's too large may be a cheating attempt, so we set it to MIN
// Can't overflow here either because should we somehow get to a 64 bit number it'll be clamped to a sane value.
if (totalDamageDealt > this->m_MaxDamage) {
totalDamageDealt = this->m_MinDamage;
}

View File

@@ -48,15 +48,13 @@ void BlockBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branc
return;
}
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
auto* const destroyableComponent = entity->GetComponent<DestroyableComponent>();
destroyableComponent->SetAttacksToBlock(this->m_numAttacksCanBlock);
if (destroyableComponent == nullptr) {
return;
if (destroyableComponent) {
// ??? what is going on here?
destroyableComponent->SetAttacksToBlock(this->m_numAttacksCanBlock);
destroyableComponent->SetAttacksToBlock(0);
}
destroyableComponent->SetAttacksToBlock(0);
}
void BlockBehavior::Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) {

View File

@@ -11,6 +11,11 @@ void ChainBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStrea
return;
}
if (chainIndex == 0) {
LOG("Received invalid chain index of 0 for behavior %i.", m_behaviorId);
return;
}
chainIndex--;
if (chainIndex < this->m_behaviors.size()) {

View File

@@ -7,6 +7,7 @@
void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_stream, const BehaviorBranchContext branch) {
auto* entity = Game::entityManager->GetEntity(branch.target);
if (!entity) return;
GameMessages::SendSetJetPackMode(entity, true, this->m_BypassChecks, this->m_EnableHover, this->m_effectId, this->m_Airspeed, this->m_MaxAirspeed, this->m_VerticalVelocity, this->m_WarningEffectID);
@@ -21,6 +22,7 @@ void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_st
void JetPackBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) {
auto* entity = Game::entityManager->GetEntity(branch.target);
if (!entity) return;
GameMessages::SendSetJetPackMode(entity, false);

View File

@@ -17,6 +17,11 @@ void SwitchMultipleBehavior::Handle(BehaviorContext* context, RakNet::BitStream&
return;
};
if (m_behaviors.empty()) {
LOG("No behaviors were loaded for %i, aborting call.", m_behaviorId);
return;
}
uint32_t trigger = 0;
for (unsigned int i = 0; i < this->m_behaviors.size(); i++) {

View File

@@ -478,6 +478,7 @@ std::vector<LWOOBJID> BaseCombatAIComponent::GetTargetWithinAggroRange() const {
for (auto id : m_Parent->GetTargetsInPhantom()) {
auto* other = Game::entityManager->GetEntity(id);
if (!other) continue;
const auto distance = Vector3::DistanceSquared(m_Parent->GetPosition(), other->GetPosition());

View File

@@ -450,19 +450,10 @@ const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffI
param.value = result.getFloatField("NumberValue");
param.effectId = result.getIntField("EffectID");
if (!result.fieldIsNull("StringValue")) {
std::istringstream stream(result.getStringField("StringValue"));
std::string token;
while (std::getline(stream, token, ',')) {
try {
const auto value = std::stof(token);
param.values.push_back(value);
} catch (std::invalid_argument& exception) {
LOG("Failed to parse value (%s): (%s)!", token.c_str(), exception.what());
}
}
for (const auto& str : GeneralUtils::SplitString(result.getStringField("StringValue"), ',')) {
if (str.empty()) continue;
const auto value = GeneralUtils::TryParse<float>(str);
if (value) param.values.push_back(value.value());
}
parameters.push_back(param);

View File

@@ -797,8 +797,14 @@ std::string CharacterComponent::StatisticsToString() const {
return result.str();
}
uint64_t CharacterComponent::GetStatisticFromSplit(std::vector<std::string> split, uint32_t index) {
return split.size() > index ? std::stoull(split.at(index)) : 0;
uint64_t CharacterComponent::GetStatisticFromSplit(const std::vector<std::string>& split, const uint32_t index) {
uint64_t toReturn = 0;
if (index < split.size()) {
const auto parsed = GeneralUtils::TryParse<uint64_t>(split[index]);
if (parsed) toReturn = *parsed;
}
return toReturn;
}
ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) {

View File

@@ -450,7 +450,7 @@ private:
* @param index the statistics ID in the string
* @return the integer value of this statistic, parsed from the string
*/
static uint64_t GetStatisticFromSplit(std::vector<std::string> split, uint32_t index);
static uint64_t GetStatisticFromSplit(const std::vector<std::string>& split, const uint32_t index);
/**
* Gets all the statistics for a certain map, if it doesn't exist, it creates empty stats
@@ -526,6 +526,7 @@ private:
/**
* Total amount of meters traveled by this character
* Should be a double and then truncated so decimals can be tracked
*/
uint64_t m_MetersTraveled;

View File

@@ -793,7 +793,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
std::vector<Entity*> scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY);
for (Entity* scriptEntity : scriptedActs) {
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
if (!zoneControl || scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
scriptEntity->GetScript()->OnPlayerDied(scriptEntity, m_Parent);
}
}
@@ -964,6 +964,8 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
if (m_Parent->IsPlayer()) {
//remove hardcore_lose_uscore_on_death_percent from the player's uscore:
auto* character = m_Parent->GetComponent<CharacterComponent>();
if (!character) return;
auto uscore = character->GetUScore();
auto uscoreToLose = static_cast<uint64_t>(uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100.0f));

View File

@@ -966,8 +966,9 @@ void InventoryComponent::EquipScripts(Item* equippedItem) {
auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name);
if (!itemScript) {
LOG("null script?");
} else {
itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId());
}
itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId());
}
}
@@ -981,8 +982,9 @@ void InventoryComponent::UnequipScripts(Item* unequippedItem) {
auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name);
if (!itemScript) {
LOG("null script?");
} else {
itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId());
}
itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId());
}
}
@@ -1633,7 +1635,7 @@ void InventoryComponent::LoadPetXml(const tinyxml2::XMLDocument& document) {
DatabasePet databasePet;
databasePet.lot = lot;
databasePet.moderationState = moderationStatus;
databasePet.name = std::string(name);
databasePet.name = name ? name : "";
SetDatabasePet(id, databasePet);

View File

@@ -22,7 +22,7 @@ public:
void NextLUPExhibit();
private:
float m_UpdateTimer = 0.0f;
std::array<LOT, 4> m_LUPExhibits = { 11121, 11295, 11423, 11979 };
const std::array<LOT, 4> m_LUPExhibits = { 11121, 11295, 11423, 11979 };
uint8_t m_LUPExhibitIndex = 0;
bool m_DirtyLUPExhibit = true;
};

View File

@@ -449,45 +449,48 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
if (mis == nullptr) return;
auto* cur = mis->FirstChildElement("cur");
auto* done = mis->FirstChildElement("done");
auto* doneM = done->FirstChildElement();
while (doneM) {
int missionId;
doneM->QueryAttribute("id", &missionId);
auto* mission = new Mission(this, missionId);
mission->LoadFromXmlDone(*doneM);
doneM = doneM->NextSiblingElement();
m_Missions.insert_or_assign(missionId, mission);
}
auto* currentM = cur->FirstChildElement();
uint32_t missionOrder{};
while (currentM) {
int missionId;
currentM->QueryAttribute("id", &missionId);
auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId);
mission->LoadFromXmlCur(*currentM);
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
mission->SetUniqueMissionOrderID(missionOrder);
if (missionOrder > m_LastUsedMissionOrderUID) m_LastUsedMissionOrderUID = missionOrder;
if (done) {
auto* doneM = done->FirstChildElement();
while (doneM) {
int missionId;
doneM->QueryAttribute("id", &missionId);
auto* mission = new Mission(this, missionId);
mission->LoadFromXmlDone(*doneM);
doneM = doneM->NextSiblingElement();
m_Missions.insert_or_assign(missionId, mission);
}
}
auto* cur = mis->FirstChildElement("cur");
if (cur) {
auto* currentM = cur->FirstChildElement();
currentM = currentM->NextSiblingElement();
uint32_t missionOrder{};
while (currentM) {
int missionId;
m_Missions.insert_or_assign(missionId, mission);
currentM->QueryAttribute("id", &missionId);
auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId);
mission->LoadFromXmlCur(*currentM);
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
mission->SetUniqueMissionOrderID(missionOrder);
if (missionOrder > m_LastUsedMissionOrderUID) m_LastUsedMissionOrderUID = missionOrder;
}
currentM = currentM->NextSiblingElement();
m_Missions.insert_or_assign(missionId, mission);
}
}
}

View File

@@ -17,7 +17,10 @@ MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent, const int
MultiZoneEntranceComponent::~MultiZoneEntranceComponent() {}
void MultiZoneEntranceComponent::OnUse(Entity* originator) {
auto* rocket = originator->GetComponent<CharacterComponent>()->RocketEquip(originator);
auto* const characterComponent = originator->GetComponent<CharacterComponent>();
if (!characterComponent) return;
auto* rocket = characterComponent->RocketEquip(originator);
if (!rocket) return;
// the LUP world menu is just the property menu, the client knows how to handle it
@@ -26,7 +29,7 @@ void MultiZoneEntranceComponent::OnUse(Entity* originator) {
void MultiZoneEntranceComponent::OnSelectWorld(Entity* originator, uint32_t index) {
auto* rocketLaunchpadControlComponent = m_Parent->GetComponent<RocketLaunchpadControlComponent>();
if (!rocketLaunchpadControlComponent) return;
if (!rocketLaunchpadControlComponent || index >= m_LUPWorlds.size()) return;
rocketLaunchpadControlComponent->Launch(originator, m_LUPWorlds[index], 0);
}

View File

@@ -922,7 +922,9 @@ void PetComponent::Deactivate() {
}
void PetComponent::Release() {
auto* inventoryComponent = GetOwner()->GetComponent<InventoryComponent>();
auto* const owner = GetOwner();
if (!owner) return;
auto* const inventoryComponent = owner->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
@@ -932,9 +934,9 @@ void PetComponent::Release() {
inventoryComponent->RemoveDatabasePet(m_DatabaseId);
auto* item = inventoryComponent->FindItemBySubKey(m_DatabaseId);
auto* const item = inventoryComponent->FindItemBySubKey(m_DatabaseId);
item->SetCount(0, false, false);
if (item) item->SetCount(0, false, false);
}
void PetComponent::Command(const NiPoint3& position, const LWOOBJID source, const int32_t commandType, const int32_t typeId, const bool overrideObey) {

View File

@@ -58,31 +58,16 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, const int32_t c
}
if (m_IsRespawnVolume) {
{
auto respawnString = std::stringstream(m_Parent->GetVarAsString(u"rspPos"));
const auto respawnPos = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"rspPos"), '\x1f');
m_RespawnPos = GeneralUtils::TryParse(respawnPos, NiPoint3Constant::ZERO);
std::string segment;
std::vector<std::string> seglist;
while (std::getline(respawnString, segment, '\x1f')) {
seglist.push_back(segment);
}
m_RespawnPos = NiPoint3(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]));
}
{
auto respawnString = std::stringstream(m_Parent->GetVarAsString(u"rspRot"));
std::string segment;
std::vector<std::string> seglist;
while (std::getline(respawnString, segment, '\x1f')) {
seglist.push_back(segment);
}
m_RespawnRot = NiQuaternion(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]), std::stof(seglist[3]));
}
const auto respawnRot = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"rspRot"), '\x1f');
m_RespawnRot = respawnRot.size() >= 4 ? NiQuaternion(
GeneralUtils::TryParse(respawnRot[0], 1.0f),
GeneralUtils::TryParse(respawnRot[1], 0.0f),
GeneralUtils::TryParse(respawnRot[2], 0.0f),
GeneralUtils::TryParse(respawnRot[3], 0.0f))
: QuatUtils::IDENTITY;
}
// HF - RespawnPoints. Legacy respawn entity.

View File

@@ -131,7 +131,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
const auto lookupResult = Database::Get()->GetProperties(propertyLookup);
for (const auto& propertyEntry : lookupResult->entries) {
for (const auto& propertyEntry : lookupResult.entries) {
const auto owner = propertyEntry.ownerId;
const auto otherCharacter = Database::Get()->GetCharacterInfo(owner);
if (!otherCharacter.has_value()) {
@@ -174,5 +174,5 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
}
// Query here is to figure out whether or not to display the button to go to the next page or not.
GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, lookupResult->totalEntriesMatchingQuery - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr);
GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, lookupResult.totalEntriesMatchingQuery - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr);
}

View File

@@ -107,20 +107,12 @@ std::vector<NiPoint3> PropertyManagementComponent::GetPaths() const {
std::vector<float> points;
std::istringstream stream(result.getStringField("path"));
std::string token;
while (std::getline(stream, token, ' ')) {
try {
auto value = std::stof(token);
points.push_back(value);
} catch (std::invalid_argument& exception) {
LOG("Failed to parse value (%s): (%s)!", token.c_str(), exception.what());
}
for (const auto& str : GeneralUtils::SplitString(result.getStringField("path"), ' ')) {
const auto value = GeneralUtils::TryParse<float>(str);
if (value) points.push_back(value.value());
}
for (auto i = 0u; i < points.size(); i += 3) {
for (auto i = 0u; i + 2 < points.size(); i += 3) {
paths.emplace_back(points[i], points[i + 1], points[i + 2]);
}
@@ -780,15 +772,17 @@ void PropertyManagementComponent::OnQueryPropertyData(Entity* originator, const
privacy = static_cast<char>(this->privacyOption);
if (moderatorRequested) {
auto moderationInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
if (moderationInfo->rejectionReason != "") {
moderatorRequested = false;
rejectionReason = moderationInfo->rejectionReason;
} else if (moderationInfo->rejectionReason == "" && moderationInfo->modApproved == 1) {
moderatorRequested = false;
rejectionReason = "";
} else {
moderatorRequested = true;
rejectionReason = "";
if (moderationInfo) {
if (moderationInfo->rejectionReason != "") {
moderatorRequested = false;
rejectionReason = moderationInfo->rejectionReason;
} else if (moderationInfo->rejectionReason == "" && moderationInfo->modApproved == 1) {
moderatorRequested = false;
rejectionReason = "";
} else {
moderatorRequested = true;
rejectionReason = "";
}
}
}
}

View File

@@ -380,7 +380,7 @@ void QuickBuildComponent::StartQuickBuild(Entity* const user) {
m_Builder = user->GetObjectID();
auto* character = user->GetComponent<CharacterComponent>();
character->SetCurrentActivity(eGameActivity::QUICKBUILDING);
if (character) character->SetCurrentActivity(eGameActivity::QUICKBUILDING);
Game::entityManager->SerializeEntity(user);

View File

@@ -306,7 +306,7 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string&
eKillType::VIOLENT, deathType, 0, 0, 90.0f, false, true, 0);
auto* destroyableComponent = vehicle->GetComponent<DestroyableComponent>();
uint32_t respawnImagination = 0;
int32_t respawnImagination = 0;
// Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live.
// Do not actually change the value yet. Do that on respawn.
if (destroyableComponent) {

View File

@@ -385,26 +385,30 @@ void TriggerComponent::HandleSetPhysicsVolumeEffect(Entity* targetEntity, std::v
}
phantomPhysicsComponent->SetPhysicsEffectActive(true);
ePhysicsEffectType effectType = ePhysicsEffectType::PUSH;
std::transform(argArray.at(0).begin(), argArray.at(0).end(), argArray.at(0).begin(), ::tolower); //Transform to lowercase
if (argArray.at(0) == "push") effectType = ePhysicsEffectType::PUSH;
else if (argArray.at(0) == "attract") effectType = ePhysicsEffectType::ATTRACT;
else if (argArray.at(0) == "repulse") effectType = ePhysicsEffectType::REPULSE;
else if (argArray.at(0) == "gravity") effectType = ePhysicsEffectType::GRAVITY_SCALE;
else if (argArray.at(0) == "friction") effectType = ePhysicsEffectType::FRICTION;
if (!argArray.empty()) {
std::transform(argArray.at(0).begin(), argArray.at(0).end(), argArray.at(0).begin(), ::tolower); //Transform to lowercase
if (argArray.at(0) == "push") effectType = ePhysicsEffectType::PUSH;
else if (argArray.at(0) == "attract") effectType = ePhysicsEffectType::ATTRACT;
else if (argArray.at(0) == "repulse") effectType = ePhysicsEffectType::REPULSE;
else if (argArray.at(0) == "gravity") effectType = ePhysicsEffectType::GRAVITY_SCALE;
else if (argArray.at(0) == "friction") effectType = ePhysicsEffectType::FRICTION;
}
phantomPhysicsComponent->SetEffectType(effectType);
phantomPhysicsComponent->SetDirectionalMultiplier(std::stof(argArray.at(1)));
if (argArray.size() > 1) {
phantomPhysicsComponent->SetDirectionalMultiplier(GeneralUtils::TryParse(argArray.at(1), 0.0f));
}
if (argArray.size() > 4) {
const NiPoint3 direction =
GeneralUtils::TryParse<NiPoint3>(argArray.at(2), argArray.at(3), argArray.at(4)).value_or(NiPoint3Constant::ZERO);
GeneralUtils::TryParse(argArray.at(2), argArray.at(3), argArray.at(4), NiPoint3Constant::ZERO);
phantomPhysicsComponent->SetDirection(direction);
}
if (argArray.size() > 5) {
const uint32_t min = GeneralUtils::TryParse<uint32_t>(argArray.at(6)).value_or(0);
if (argArray.size() > 6) {
const uint32_t min = GeneralUtils::TryParse(argArray.at(6), 0);
phantomPhysicsComponent->SetMin(min);
const uint32_t max = GeneralUtils::TryParse<uint32_t>(argArray.at(7)).value_or(0);
const uint32_t max = GeneralUtils::TryParse(argArray.at(7), 0);
phantomPhysicsComponent->SetMax(max);
}

View File

@@ -61,6 +61,11 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
User* usr = UserManager::Instance()->GetUser(sysAddr);
if (!usr) {
LOG("Failed to find a logged in user for (%llu), aborting GM: %4i, %s!", objectID, messageID, StringifiedEnum::ToString(messageID).data());
return;
}
if (!entity) {
LOG("Failed to find associated entity (%llu), aborting GM: %4i, %s!", objectID, messageID, StringifiedEnum::ToString(messageID).data());
return;
@@ -76,7 +81,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
if (msg->requiredGmLevel > eGameMasterLevel::CIVILIAN) {
auto* usingEntity = Game::entityManager->GetEntity(usr->GetLoggedInChar());
if (!usingEntity || usingEntity->GetGMLevel() < msg->requiredGmLevel) {
LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID());
if (usingEntity) LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID());
else LOG("ObjectID %llu tried to use a gm required message.", usr->GetLoggedInChar());
return;
}
}
@@ -167,8 +173,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
GameMessages::SendRestoreToPostLoadStats(entity, sysAddr);
auto* destroyable = entity->GetComponent<DestroyableComponent>();
destroyable->SetImagination(destroyable->GetImagination());
auto* const destroyable = entity->GetComponent<DestroyableComponent>();
if (destroyable) destroyable->SetImagination(destroyable->GetImagination());
Game::entityManager->SerializeEntity(entity);
std::vector<Entity*> racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
@@ -186,7 +192,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
std::vector<Entity*> scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPT);
for (Entity* scriptEntity : scriptedActs) {
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
if (!zoneControl || scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
scriptEntity->GetScript()->OnPlayerLoaded(scriptEntity, entity);
}
}
@@ -332,9 +338,9 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
if (behaviorId > 0) {
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&startSkill.sBitStream[0]), startSkill.sBitStream.size(), false);
auto* skillComponent = entity->GetComponent<SkillComponent>();
auto* const skillComponent = entity->GetComponent<SkillComponent>();
success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID);
if (skillComponent) success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID);
if (success && entity->GetCharacter()) {
DestroyableComponent* destComp = entity->GetComponent<DestroyableComponent>();
@@ -387,9 +393,9 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
if (usr != nullptr) {
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&sync.sBitStream[0]), sync.sBitStream.size(), false);
auto* skillComponent = entity->GetComponent<SkillComponent>();
auto* const skillComponent = entity->GetComponent<SkillComponent>();
skillComponent->SyncPlayerSkill(sync.uiSkillHandle, sync.uiBehaviorHandle, bs);
if (skillComponent) skillComponent->SyncPlayerSkill(sync.uiSkillHandle, sync.uiBehaviorHandle, bs);
}
EchoSyncSkill echo = EchoSyncSkill();

View File

@@ -2137,6 +2137,7 @@ void GameMessages::HandleUpdatePropertyOrModelForFilterCheck(RakNet::BitStream&
inStream.Read(worldId);
inStream.Read(descriptionLength);
if (descriptionLength > MAX_MESSAGE_LENGTH) return;
for (uint32_t i = 0; i < descriptionLength; ++i) {
uint16_t character;
inStream.Read(character);
@@ -2144,6 +2145,7 @@ void GameMessages::HandleUpdatePropertyOrModelForFilterCheck(RakNet::BitStream&
}
inStream.Read(nameLength);
if (nameLength > MAX_MESSAGE_LENGTH) return;
for (uint32_t i = 0; i < nameLength; ++i) {
uint16_t character;
inStream.Read(character);
@@ -2474,9 +2476,15 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
uint32_t sd0Size;
inStream.Read(sd0Size);
std::unique_ptr<char[]> sd0Data(new char[sd0Size]);
if (sd0Data == nullptr) return;
// For the sake of letting players make models as big as they want, only reject if we cant allocate the required memory.
std::unique_ptr<char[]> sd0Data;
try {
sd0Data.reset(new char[sd0Size]);
} catch (std::exception& e) {
LOG("Failed to allocate sd0 of size %u", sd0Size);
return;
}
inStream.ReadAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size);
@@ -2652,6 +2660,7 @@ void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entit
inStream.Read(startIndex);
inStream.Read(filterTextLength);
if (filterTextLength > MAX_MESSAGE_LENGTH) return;
for (auto i = 0u; i < filterTextLength; i++) {
char c;
inStream.Read(c);
@@ -3040,6 +3049,7 @@ void GameMessages::HandleVerifyAck(RakNet::BitStream& inStream, Entity* entity,
uint32_t sBitStreamLength = 0;
inStream.Read(sBitStreamLength);
if (sBitStreamLength > MAX_MESSAGE_LENGTH) return;
for (uint64_t k = 0; k < sBitStreamLength; k++) {
uint8_t character;
inStream.Read(character);
@@ -3261,6 +3271,7 @@ void GameMessages::HandleClientTradeUpdate(RakNet::BitStream& inStream, Entity*
inStream.Read(currency);
inStream.Read(itemCount);
if (itemCount > MAX_MESSAGE_LENGTH) return;
LOG("Trade update from (%llu) -> (%llu), (%i)", entity->GetObjectID(), currency, itemCount);
@@ -3673,6 +3684,7 @@ void GameMessages::HandleRequestSetPetName(RakNet::BitStream& inStream, Entity*
inStream.Read(nameLength);
if (nameLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < nameLength; i++) {
char16_t character;
inStream.Read(character);
@@ -3742,6 +3754,7 @@ void GameMessages::HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity*
inStream.Read(iButton);
inStream.Read(identifierLength);
if (identifierLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < identifierLength; i++) {
char16_t character;
inStream.Read(character);
@@ -3749,6 +3762,7 @@ void GameMessages::HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity*
}
inStream.Read(userDataLength);
if (userDataLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < userDataLength; i++) {
char16_t character;
inStream.Read(character);
@@ -3796,6 +3810,7 @@ void GameMessages::HandleChoiceBoxRespond(RakNet::BitStream& inStream, Entity* e
std::u16string identifier;
inStream.Read(buttonIdentifierLength);
if (buttonIdentifierLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < buttonIdentifierLength; i++) {
char16_t character;
inStream.Read(character);
@@ -3805,6 +3820,7 @@ void GameMessages::HandleChoiceBoxRespond(RakNet::BitStream& inStream, Entity* e
inStream.Read(iButton);
inStream.Read(identifierLength);
if (identifierLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < identifierLength; i++) {
char16_t character;
inStream.Read(character);
@@ -4158,7 +4174,13 @@ void GameMessages::HandleUpdatePropertyPerformanceCost(RakNet::BitStream& inStre
return;
}
Database::Get()->UpdatePerformanceCost(zone->GetZoneID(), performanceCost);
const auto* const propertyManagementComponent = entity->GetComponent<PropertyManagementComponent>();
const auto* const ownerEntity = propertyManagementComponent ? propertyManagementComponent->GetOwner() : nullptr;
const auto* const character = ownerEntity ? ownerEntity->GetCharacter() : nullptr;
const auto& zoneID = zone->GetZoneID();
if (character && character->GetPropertyCloneID() == zoneID.GetCloneID()) {
Database::Get()->UpdatePerformanceCost(zoneID, performanceCost);
}
}
void GameMessages::HandleVehicleNotifyHitImaginationServer(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
@@ -4783,13 +4805,19 @@ void GameMessages::HandleParseChatMessage(RakNet::BitStream& inStream, Entity* e
uint32_t wsStringLength;
inStream.Read(wsStringLength);
if (wsStringLength > MAX_MESSAGE_LENGTH) {
LOG("Max message length reached, capping message.");
wsStringLength = MAX_MESSAGE_LENGTH;
}
for (uint32_t i = 0; i < wsStringLength; ++i) {
uint16_t character;
inStream.Read(character);
wsString.push_back(character);
}
if (wsString[0] == L'/') {
if (!wsString.empty() && wsString[0] == L'/') {
SlashCommandHandler::HandleChatCommand(wsString, entity, sysAddr);
}
}
@@ -4806,6 +4834,7 @@ void GameMessages::HandleFireEventServerSide(RakNet::BitStream& inStream, Entity
LWOOBJID senderID{};
inStream.Read(argsLength);
if (argsLength > MAX_MESSAGE_LENGTH) return;
for (uint32_t i = 0; i < argsLength; ++i) {
uint16_t character;
inStream.Read(character);
@@ -5436,7 +5465,7 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
std::vector<LOT> modList;
auto& oldPartList = character->GetVar<std::string>(u"currentModifiedBuild");
bool everyPieceSwapped = !oldPartList.empty(); // If the player didn't put a build in initially, then they should not get this achievement.
if (count >= 3) {
if (count >= 3 && count < 8) {
std::u16string modules;
for (uint32_t k = 0; k < count; k++) {
@@ -5733,6 +5762,7 @@ void GameMessages::HandleMatchRequest(RakNet::BitStream& inStream, Entity* entit
inStream.Read(activator);
inStream.Read(playerChoicesLen);
if (playerChoicesLen > MAX_MESSAGE_LENGTH) return;
for (uint32_t i = 0; i < playerChoicesLen; ++i) {
uint16_t character;
inStream.Read(character);
@@ -5890,7 +5920,7 @@ void GameMessages::HandlePlayerRailArrivedNotification(RakNet::BitStream& inStre
const SystemAddress& sysAddr) {
uint32_t pathNameLength;
inStream.Read(pathNameLength);
if (pathNameLength > MAX_MESSAGE_LENGTH) return;
std::u16string pathName;
for (auto k = 0; k < pathNameLength; k++) {
uint16_t c;

View File

@@ -75,7 +75,8 @@ uint32_t Inventory::GetLotCount(const LOT lot) const {
}
void Inventory::SetSize(const uint32_t value) {
free += static_cast<int32_t>(value) - static_cast<int32_t>(size);
const auto delta = static_cast<int32_t>(value) - static_cast<int32_t>(size);
free = static_cast<uint32_t>(std::max(0, static_cast<int32_t>(free) + delta));
size = value;

View File

@@ -401,7 +401,8 @@ void Item::Disassemble(const eInventoryType inventoryType) {
const auto deliminator = '+';
while (std::getline(ssData, token, deliminator)) {
const auto modLot = std::stoi(token.substr(2, token.size() - 1));
if (token.size() <= 2) continue; // invalid token, must have size of at least 3.
const auto modLot = GeneralUtils::TryParse(token.substr(2, token.size() - 1), LOT_NULL);
modArray.push_back(modLot);
}
@@ -440,7 +441,10 @@ void Item::DisassembleModel(uint32_t numToDismantle) {
std::vector<std::string> renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/');
if (renderAssetSplit.empty()) return;
std::string lxfmlPath = "BrickModels" + lxfmlFolderName + "/" + GeneralUtils::SplitString(renderAssetSplit.back(), '.').at(0) + ".lxfml";
const auto renderAssetSplitSplit = GeneralUtils::SplitString(renderAssetSplit.back(), '.');
if (renderAssetSplitSplit.empty()) return;
std::string lxfmlPath = "BrickModels" + lxfmlFolderName + "/" + renderAssetSplitSplit[0] + ".lxfml";
auto file = Game::assetManager->GetFile(lxfmlPath.c_str());
if (!file) {

View File

@@ -128,8 +128,8 @@ void ItemSet::OnEquip(const LOT lot) {
return;
}
auto* skillComponent = m_InventoryComponent->GetParent()->GetComponent<SkillComponent>();
auto* missionComponent = m_InventoryComponent->GetParent()->GetComponent<MissionComponent>();
auto [skillComponent, missionComponent] = m_InventoryComponent->GetParent()->GetComponentsMut<SkillComponent, MissionComponent>();
if (!skillComponent || !missionComponent) return; // Nothing to do here if these are null
for (const auto skill : skillSet) {
auto* skillTable = CDClientManager::GetTable<CDSkillBehaviorTable>();

View File

@@ -74,23 +74,19 @@ Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) {
void Mission::LoadFromXmlDone(const tinyxml2::XMLElement& element) {
// Start custom XML
if (element.Attribute("state") != nullptr) {
m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state")));
}
m_State = static_cast<eMissionState>(element.UnsignedAttribute("state"));
// End custom XML
if (element.Attribute("cct") != nullptr) {
m_Completions = std::stoul(element.Attribute("cct"));
m_Completions = element.UnsignedAttribute("cct");
m_Timestamp = std::stoul(element.Attribute("cts"));
}
m_Timestamp = element.UnsignedAttribute("cts");
}
void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
const auto* const character = GetCharacter();
// Start custom XML
if (element.Attribute("state") != nullptr) {
m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state")));
m_State = static_cast<eMissionState>(element.IntAttribute("state", -1));
}
// End custom XML
@@ -106,7 +102,7 @@ void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
const auto type = curTask->GetType();
auto value = std::stoul(task->Attribute("v"));
auto value = task->UnsignedAttribute("v");
curTask->SetProgress(value, false);
task = task->NextSiblingElement();
@@ -114,7 +110,7 @@ void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
if (type == eMissionTaskType::COLLECTION || type == eMissionTaskType::VISIT_PROPERTY) {
std::vector<uint32_t> uniques;
while (task != nullptr && value > 0) {
const auto unique = std::stoul(task->Attribute("v"));
const auto unique = task->UnsignedAttribute("v");
uniques.push_back(unique);

View File

@@ -17,50 +17,63 @@ PropertyBehavior::PropertyBehavior(bool _isTemplated) {
isTemplated = _isTemplated;
}
bool CheckStateRange(const BehaviorState state) {
return state >= BehaviorState::HOME_STATE && state <= BehaviorState::STAR_STATE;
}
template<>
void PropertyBehavior::HandleMsg(AddStripMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId();
};
template<>
void PropertyBehavior::HandleMsg(AddActionMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId();
};
template<>
void PropertyBehavior::HandleMsg(RearrangeStripMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId();
};
template<>
void PropertyBehavior::HandleMsg(UpdateActionMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId();
};
template<>
void PropertyBehavior::HandleMsg(UpdateStripUiMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId();
};
template<>
void PropertyBehavior::HandleMsg(RemoveStripMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId();
};
template<>
void PropertyBehavior::HandleMsg(RemoveActionsMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId();
};
template<>
void PropertyBehavior::HandleMsg(SplitStripMessage& msg) {
if (!CheckStateRange(msg.GetSourceActionContext().GetStateId())) return;
if (!CheckStateRange(msg.GetDestinationActionContext().GetStateId())) return;
m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg);
m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetDestinationActionContext().GetStateId();
@@ -68,6 +81,8 @@ void PropertyBehavior::HandleMsg(SplitStripMessage& msg) {
template<>
void PropertyBehavior::HandleMsg(MigrateActionsMessage& msg) {
if (!CheckStateRange(msg.GetSourceActionContext().GetStateId())) return;
if (!CheckStateRange(msg.GetDestinationActionContext().GetStateId())) return;
m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg);
m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetDestinationActionContext().GetStateId();
@@ -75,6 +90,8 @@ void PropertyBehavior::HandleMsg(MigrateActionsMessage& msg) {
template<>
void PropertyBehavior::HandleMsg(MergeStripsMessage& msg) {
if (!CheckStateRange(msg.GetSourceActionContext().GetStateId())) return;
if (!CheckStateRange(msg.GetDestinationActionContext().GetStateId())) return;
m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg);
m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetDestinationActionContext().GetStateId();

View File

@@ -66,13 +66,9 @@ const BrickList& BrickDatabase::GetBricks(const LxfmlPath& lxfmlPath) {
std::string materialString(materialList);
const auto materials = GeneralUtils::SplitString(materialString, ',');
if (!materials.empty()) {
brick.materialID = std::stoi(materials[0]);
} else {
brick.materialID = 0;
}
brick.materialID = GeneralUtils::TryParse(materials[0], 0);
} else if (materialID != nullptr) {
brick.materialID = std::stoi(materialID);
brick.materialID = GeneralUtils::TryParse(materialID, 0);
} else {
brick.materialID = 0; // This is bad, makes it so the minigame can't be played
}

View File

@@ -54,17 +54,23 @@ void LogAndSaveFailedAntiCheatCheck(const LWOOBJID& id, const SystemAddress& sys
// If player exists and entity exists in world, use both for logging info.
if (entity && player) {
const auto* const playerChar = player->GetCharacter();
const auto& playerName = playerChar ? playerChar->GetName() : "(null player character)";
const auto* const entityChar = entity->GetCharacter();
const auto& entityName = entityChar ? entityChar->GetName() : "(null entity character)";
LOG("Player (%s) (%llu) at system address (%s) with sending player (%s) (%llu) does not match their own.",
player->GetCharacter()->GetName().c_str(), player->GetObjectID(),
playerName.c_str(), player->GetObjectID(),
sysAddr.ToString(),
entity->GetCharacter()->GetName().c_str(), entity->GetObjectID());
if (player->GetCharacter()) toReport = player->GetCharacter()->GetParentUser();
entityName.c_str(), entity->GetObjectID());
if (playerChar) toReport = playerChar->GetParentUser();
// In the case that the target entity id did not exist, just log the player info.
} else if (player) {
const auto* const playerChar = player->GetCharacter();
const auto& playerName = playerChar ? playerChar->GetName() : "(null player character)";
LOG("Player (%s) (%llu) at system address (%s) with sending player (%llu) does not match their own.",
player->GetCharacter()->GetName().c_str(), player->GetObjectID(),
playerName.c_str(), player->GetObjectID(),
sysAddr.ToString(), id);
if (player->GetCharacter()) toReport = player->GetCharacter()->GetParentUser();
if (playerChar) toReport = playerChar->GetParentUser();
// In the rare case that the player does not exist, just log the system address and who the target id was.
} else {
LOG("Player at system address (%s) with sending player (%llu) does not match their own.",
@@ -76,8 +82,11 @@ void LogAndSaveFailedAntiCheatCheck(const LWOOBJID& id, const SystemAddress& sys
auto* user = UserManager::Instance()->GetUser(sysAddr);
if (user) {
const auto* const lastChar = user->GetLastUsedChar();
const auto& lastName = lastChar ? lastChar->GetName() : "(null last char)";
const auto lastObjID = lastChar ? lastChar->GetObjectID() : LWOOBJID_EMPTY;
LOG("User at system address (%s) (%s) (%llu) sent a packet as (%llu) which is not an id they own.",
sysAddr.ToString(), user->GetLastUsedChar()->GetName().c_str(), user->GetLastUsedChar()->GetObjectID(), id);
sysAddr.ToString(), lastName.c_str(), lastObjID, id);
// Can't know sending player. Just log system address for IP banning.
} else {
LOG("No user found for system address (%s).", sysAddr.ToString());

View File

@@ -326,7 +326,7 @@ void DropRegularLoot(Team& team, GameMessages::DropClientLoot& lootMsg, const bo
void DropLoot(Entity* player, const LWOOBJID source, const std::map<LOT, LootDropInfo>& rolledItems, uint32_t minCoins, uint32_t maxCoins, const bool noTeamLootOnDeath) {
player = player->GetOwner(); // if the owner is overwritten, we collect that here
const auto playerID = player->GetObjectID();
if (!player || !player->IsPlayer()) {
if (!player->IsPlayer()) {
LOG("Trying to drop loot for non-player %llu:%i", playerID, player->GetLOT());
return;
}

View File

@@ -74,69 +74,74 @@ namespace Mail {
void SendRequest::Handle() {
SendResponse response;
auto* character = player->GetCharacter();
const bool restrictMailOnMute = UserManager::Instance()->GetMuteRestrictMail() && character->GetParentUser()->GetIsMuted();
const bool restrictedMailAccess = character->HasPermission(ePermissionMap::RestrictedMailAccess);
if (character && !(restrictedMailAccess || restrictMailOnMute)) {
mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), "");
auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient);
if (!receiverID) {
response.status = eSendResponse::RecipientNotFound;
} else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) {
response.status = eSendResponse::CannotMailSelf;
} else {
uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee;
uint32_t stackSize = 0;
auto inventoryComponent = player->GetComponent<InventoryComponent>();
Item* item = nullptr;
bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0;
if (hasAttachment) {
item = inventoryComponent->FindItemById(mailInfo.itemID);
if (item) {
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig().mailPercentAttachmentFee);
mailInfo.itemLOT = item->GetLot();
}
}
if (hasAttachment && !item) {
response.status = eSendResponse::AttachmentNotFound;
} else if (player->GetCharacter()->GetCoins() - mailCost < 0) {
response.status = eSendResponse::NotEnoughCoins;
} else {
bool removeSuccess = true;
// Remove coins and items from the sender
player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
if (inventoryComponent && hasAttachment && item) {
removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, ALL, true);
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount);
}
// we passed all the checks, now we can actully send the mail
if (removeSuccess) {
mailInfo.senderId = character->GetID();
mailInfo.senderUsername = character->GetName();
mailInfo.receiverId = receiverID->id;
mailInfo.itemSubkey = LWOOBJID_EMPTY;
//clear out the attachementID
mailInfo.itemID = 0;
Database::Get()->InsertNewMail(mailInfo);
response.status = eSendResponse::Success;
character->SaveXMLToDatabase();
} else {
response.status = eSendResponse::AttachmentNotFound;
}
}
}
if (!character) {
response.status = eSendResponse::UnknownError;
} else {
response.status = eSendResponse::SenderAccountIsMuted;
const bool restrictMailOnMute = UserManager::Instance()->GetMuteRestrictMail() && character->GetParentUser()->GetIsMuted();
const bool restrictedMailAccess = character->HasPermission(ePermissionMap::RestrictedMailAccess);
if (character && !(restrictedMailAccess || restrictMailOnMute)) {
mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), "");
auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient);
if (!receiverID) {
response.status = eSendResponse::RecipientNotFound;
} else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) {
response.status = eSendResponse::CannotMailSelf;
} else {
uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee;
uint32_t stackSize = 0;
auto inventoryComponent = player->GetComponent<InventoryComponent>();
Item* item = nullptr;
bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0;
if (hasAttachment) {
item = inventoryComponent->FindItemById(mailInfo.itemID);
if (item) {
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig().mailPercentAttachmentFee);
mailInfo.itemLOT = item->GetLot();
}
}
if (hasAttachment && !item) {
response.status = eSendResponse::AttachmentNotFound;
} else if (player->GetCharacter()->GetCoins() - mailCost < 0) {
response.status = eSendResponse::NotEnoughCoins;
} else {
bool removeSuccess = true;
// Remove coins and items from the sender
player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
if (inventoryComponent && hasAttachment && item) {
removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, ALL, true);
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount);
}
// we passed all the checks, now we can actully send the mail
if (removeSuccess) {
mailInfo.senderId = character->GetID();
mailInfo.senderUsername = character->GetName();
mailInfo.receiverId = receiverID->id;
mailInfo.itemSubkey = LWOOBJID_EMPTY;
//clear out the attachementID
mailInfo.itemID = 0;
Database::Get()->InsertNewMail(mailInfo);
response.status = eSendResponse::Success;
character->SaveXMLToDatabase();
} else {
response.status = eSendResponse::AttachmentNotFound;
}
}
}
} else {
response.status = eSendResponse::SenderAccountIsMuted;
}
}
LOG("Finished send with status %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr);
}
@@ -193,7 +198,7 @@ namespace Mail {
if (mailID > 0 && playerID == player->GetObjectID() && inv) {
auto playerMail = Database::Get()->GetMail(mailID);
if (!playerMail) {
if (!playerMail || playerMail->receiverId != player->GetObjectID()) {
response.status = eAttachmentCollectResponse::MailNotFound;
} else if (!inv->HasSpaceForLoot({ {playerMail->itemLOT, playerMail->itemCount} })) {
response.status = eAttachmentCollectResponse::NoSpaceInInventory;
@@ -225,15 +230,21 @@ namespace Mail {
DeleteResponse response;
response.mailID = mailID;
auto mailData = Database::Get()->GetMail(mailID);
if (mailData && !(mailData->itemLOT > 0 && mailData->itemCount > 0)) {
Database::Get()->DeleteMail(mailID);
response.status = eDeleteResponse::Success;
} else if (mailData && mailData->itemLOT > 0 && mailData->itemCount > 0) {
response.status = eDeleteResponse::HasAttachments;
} else {
response.status = eDeleteResponse::NotFound;
const auto mailData = Database::Get()->GetMail(mailID);
response.status = eDeleteResponse::NotFound;
if (mailData) {
if (mailData->receiverId != playerID) {
LOG("Player %llu attempted to delete mail owned by %llu. Possible spoof?", playerID, mailData->receiverId);
} else {
if (!(mailData->itemLOT > 0 && mailData->itemCount > 0)) {
Database::Get()->DeleteMail(mailID);
response.status = eDeleteResponse::Success;
} else if (mailData->itemLOT > 0 && mailData->itemCount > 0) {
response.status = eDeleteResponse::HasAttachments;
}
}
}
LOG("DeleteRequest status %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr);
}
@@ -253,11 +264,19 @@ namespace Mail {
void ReadRequest::Handle() {
ReadResponse response;
response.status = eReadResponse::UnknownError;
response.mailID = mailID;
if (Database::Get()->GetMail(mailID)) {
response.status = eReadResponse::Success;
Database::Get()->MarkMailRead(mailID);
const auto mail = Database::Get()->GetMail(mailID);
if (mail) {
if (mail->receiverId == player->GetObjectID()) {
response.status = eReadResponse::Success;
Database::Get()->MarkMailRead(mailID);
} else {
LOG("Player %llu tried to mark mail read for player %llu", mail->receiverId, player->GetObjectID());
}
} else {
LOG("No mail by ID %llu found to mark as read.", mailID);
}
LOG("ReadRequest %s", StringifiedEnum::ToString(response.status).data());

View File

@@ -114,11 +114,13 @@ bool Precondition::Check(Entity* player, bool evaluateCosts) const {
bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluateCosts) const {
auto* missionComponent = player->GetComponent<MissionComponent>();
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
auto* destroyableComponent = player->GetComponent<DestroyableComponent>();
auto* levelComponent = player->GetComponent<LevelProgressionComponent>();
auto* character = player->GetCharacter();
auto [missionComponent, inventoryComponent, destroyableComponent, levelComponent] =
player->GetComponentsMut<const MissionComponent, /* not const */ InventoryComponent, const DestroyableComponent, const LevelProgressionComponent>();
if (!missionComponent || !inventoryComponent || !destroyableComponent || !levelComponent || !character) {
return false;
}
Mission* mission;

View File

@@ -825,7 +825,7 @@ namespace DEVGMCommands {
}
const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[1]);
if (!numberToSpawnOptional && numberToSpawnOptional.value() > 0) {
if (!numberToSpawnOptional) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn.");
return;
}
@@ -833,7 +833,7 @@ namespace DEVGMCommands {
// Must spawn within a radius of at least 0.0f
const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(splitArgs[2]);
if (!radiusToSpawnWithinOptional && radiusToSpawnWithinOptional.value() < 0.0f) {
if (!radiusToSpawnWithinOptional || radiusToSpawnWithinOptional.value() < 0.0f) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within.");
return;
}
@@ -1133,6 +1133,10 @@ namespace DEVGMCommands {
}
const auto& password = splitArgs[2];
if (password.length() >= 50) {
ChatPackets::SendSystemMessage(sysAddr, u"Password is too long.");
return;
}
ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password);

View File

@@ -187,8 +187,13 @@ namespace GMZeroCommands {
auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map...");
const auto& password = splitArgs[0];
if (password.length() >= 50) {
ChatPackets::SendSystemMessage(sysAddr, u"Password is too long.");
return;
}
ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map...");
ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);