Skip to content

Instantly share code, notes, and snippets.

@rookgaard
Last active August 27, 2023 21:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rookgaard/64834fc1de5414ea625cd21b3e9f427a to your computer and use it in GitHub Desktop.
Save rookgaard/64834fc1de5414ea625cd21b3e9f427a to your computer and use it in GitHub Desktop.
cams by gesior
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5540fa58..48439138 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,6 +4,7 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/ban.cpp
${CMAKE_CURRENT_LIST_DIR}/baseevents.cpp
${CMAKE_CURRENT_LIST_DIR}/bed.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/cams.cpp
${CMAKE_CURRENT_LIST_DIR}/chat.cpp
${CMAKE_CURRENT_LIST_DIR}/combat.cpp
${CMAKE_CURRENT_LIST_DIR}/condition.cpp
diff --git a/src/cams.cpp b/src/cams.cpp
new file mode 100644
index 00000000..1916b5a0
--- /dev/null
+++ b/src/cams.cpp
@@ -0,0 +1,216 @@
+#include "otpch.h"
+
+#include "cams.h"
+#include "configmanager.h"
+
+extern ConfigManager g_config;
+
+void Cams::threadMain()
+{
+ if (!g_config.getBoolean(ConfigManager::CAMS_ENABLED)) {
+ camSystemEnabled = false;
+ std::cout << "Cams System disabled" << std::endl;
+ return;
+ }
+
+ boost::filesystem::path camsDirectory(g_config.getString(ConfigManager::CAMS_DIRECTORY));
+
+ if (!boost::filesystem::exists(camsDirectory) || !boost::filesystem::is_directory(camsDirectory)) {
+ camSystemEnabled = false;
+ std::cout << "[Warning - Cams::threadMain] Configured Cams directory does not exist or is not a directory: '";
+ std::cout << camsDirectory << "'\n" << "[Warning - Cams::threadMain] Cam System disabled" << std::endl;
+ return;
+ }
+
+ while (true) {
+ processAllCams(camsDirectory, false);
+ std::this_thread::sleep_for(std::chrono::milliseconds(250));
+
+ if (getState() == THREAD_STATE_TERMINATED) {
+ processAllCams(camsDirectory, true);
+ break;
+ }
+ }
+}
+
+void Cams::processAllCams(const boost::filesystem::path &camsDirectory, bool serverShutdown)
+{
+ auto *playerCamsToWriteToDisk = new std::vector<PlayerCam>();
+ auto *playerCamsToClose = new std::vector<PlayerCam>();
+ generateCamsToProcessLists(playerCamsToWriteToDisk, playerCamsToClose, serverShutdown);
+
+ writeCamsToDisk(playerCamsToWriteToDisk, camsDirectory);
+ playerCamsToWriteToDisk->clear();
+
+ closeCams(playerCamsToClose, camsDirectory);
+ playerCamsToClose->clear();
+ delete playerCamsToWriteToDisk;
+ delete playerCamsToClose;
+}
+
+void Cams::generateCamsToProcessLists(std::vector<PlayerCam> *playerCamsToWriteToDisk,
+ std::vector<PlayerCam> *playerCamsToClose,
+ bool closeAllCams)
+{
+ int memoryBufferPacketsNumber = g_config.getNumber(ConfigManager::CAMS_MEMORY_BUFFER_PACKETS_NUMBER);
+ int closeCamIfNoPacketsForSeconds = g_config.getNumber(ConfigManager::CAMS_CLOSE_CAM_IF_NO_PACKETS_FOR_SECONDS);
+
+ std::lock_guard<std::mutex> lockClass(playerCamsLock);
+
+ for (auto it = playerCams.begin(); it != playerCams.end();) {
+ auto playerCam = it->second;
+ if (playerCam.lastPacket + closeCamIfNoPacketsForSeconds < time(nullptr) || closeAllCams) {
+ playerCamsToWriteToDisk->push_back(playerCam);
+ playerCamsToClose->push_back(playerCam);
+ it = playerCams.erase(it);
+ } else if (playerCam.packets->size() > memoryBufferPacketsNumber) {
+ playerCamsToWriteToDisk->push_back(playerCam);
+ playerCam.packets = std::make_shared<PacketsQueue>();
+ ++it;
+ } else {
+ ++it;
+ }
+ }
+}
+
+void Cams::writeCamsToDisk(std::vector<PlayerCam> *playerCamsToWriteToDisk,
+ const boost::filesystem::path &camsDirectory)
+{
+ for (auto playerCam: *playerCamsToWriteToDisk) {
+ std::string camTmpFilePath = getCamTmpFilePath(camsDirectory, playerCam);
+
+ std::ofstream camFileOutput(camTmpFilePath, std::ofstream::out | std::ofstream::app);
+ if (!camFileOutput.is_open()) {
+ std::cout << "[Warning - Cams::threadMain] Cannot open '" << camTmpFilePath << "'" << std::endl;
+ continue;
+ }
+
+ auto packets = playerCam.packets;
+ while (!packets->empty()) {
+ auto packet = packets->front();
+ packets->pop();
+
+ if (packet.type == TYPE_INPUT) {
+ camFileOutput << ">";
+ } else {
+ camFileOutput << "<";
+ }
+ camFileOutput << " " << (packet.time - playerCam.startTime) << " ";
+ camFileOutput << std::hex;
+ for (uint8_t &byte: packet.bytes) {
+ camFileOutput << (int) (byte / 16) << (int) (byte % 16);
+ }
+ camFileOutput << std::endl;
+ camFileOutput << std::dec;
+ }
+ camFileOutput.close();
+ }
+}
+
+void Cams::closeCams(std::vector<PlayerCam> *playerCamsToClose, const boost::filesystem::path &camsDirectory)
+{
+ for (auto playerCam: *playerCamsToClose) {
+ std::string camTmpFilePath = getCamTmpFilePath(camsDirectory, playerCam);
+ std::string camFilePath = getCamFilePath(camsDirectory, playerCam);
+ if (std::rename(camTmpFilePath.c_str(), camFilePath.c_str())) {
+ std::cout << "[Warning - Cams::threadMain] Failed to move temporary Cam file from '"
+ << camTmpFilePath << "' to '" << camFilePath << "'" << std::endl;
+ }
+ }
+}
+
+std::string Cams::getCamFilePath(const boost::filesystem::path &camsDirectory, PlayerCam &playerCam)
+{
+ boost::filesystem::path camPath = camsDirectory;
+ camPath /= std::to_string(playerCam.playerId) + "." + std::to_string(playerCam.startTime) + ".cam";
+ return camPath.string();
+}
+
+std::string Cams::getCamTmpFilePath(const boost::filesystem::path &camsDirectory, PlayerCam &playerCam)
+{
+ boost::filesystem::path camPath = camsDirectory;
+ camPath /= std::to_string(playerCam.playerId) + "." + std::to_string(playerCam.startTime) + ".cam.tmp";
+ return camPath.string();
+}
+
+uint64_t Cams::startCam(uint32_t playerId, uint32_t playerLevel, uint32_t accountId, uint32_t ip)
+{
+ if (!camSystemEnabled) {
+ return 0;
+ }
+
+ std::lock_guard<std::mutex> lockClass(playerCamsLock);
+
+ ++currentCamId;
+ playerCams[currentCamId] = PlayerCam(
+ {
+ currentCamId,
+ std::make_shared<PacketsQueue>(),
+ OTSYS_TIME(),
+ time(nullptr),
+ playerId,
+ playerLevel,
+ accountId,
+ ip
+ }
+ );
+
+ return currentCamId;
+}
+
+void Cams::addInputPacket(uint64_t camId, const NetworkMessage &msg)
+{
+ if (!camSystemEnabled) {
+ return;
+ }
+
+ if (!g_config.getBoolean(ConfigManager::CAMS_RECORD_INPUT_PACKETS)) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lockClass(playerCamsLock);
+
+ auto it = playerCams.find(camId);
+ if (it == playerCams.end()) {
+ return;
+ }
+
+ int startBufferPosition = msg.getBufferPosition() - 1;
+ int lastBufferPosition = msg.getBufferPosition() + msg.getLength() - 1;
+ std::vector<uint8_t> buffer(msg.getBuffer() + startBufferPosition, msg.getBuffer() + lastBufferPosition);
+
+ it->second.lastPacket = time(nullptr);
+ it->second.packets->push(Packet({OTSYS_TIME(), TYPE_INPUT, std::move(buffer)}));
+}
+
+void Cams::addOutputPacket(uint64_t camId, const NetworkMessage &msg)
+{
+ if (!camSystemEnabled) {
+ return;
+ }
+
+ if (!g_config.getBoolean(ConfigManager::CAMS_RECORD_OUTPUT_PACKETS)) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lockClass(playerCamsLock);
+
+ auto it = playerCams.find(camId);
+ if (it == playerCams.end()) {
+ return;
+ }
+
+ int startBufferPosition = NetworkMessage::INITIAL_BUFFER_POSITION;
+ int lastBufferPosition = msg.getBufferPosition();
+ std::vector<uint8_t> buffer(msg.getBuffer() + startBufferPosition, msg.getBuffer() + lastBufferPosition);
+
+ it->second.lastPacket = time(nullptr);
+ it->second.packets->push(Packet({OTSYS_TIME(), TYPE_OUTPUT, std::move(buffer)}));
+}
+
+void Cams::shutdown()
+{
+ std::lock_guard<std::mutex> lockClass(playerCamsLock);
+
+ setState(THREAD_STATE_TERMINATED);
+}
diff --git a/src/cams.h b/src/cams.h
new file mode 100644
index 00000000..0baaf64f
--- /dev/null
+++ b/src/cams.h
@@ -0,0 +1,65 @@
+#ifndef FS_CAMS_H
+#define FS_CAMS_H
+
+#include <condition_variable>
+#include <map>
+#include <vector>
+#include <queue>
+#include <boost/filesystem.hpp>
+#include "thread_holder_base.h"
+#include "enums.h"
+#include "networkmessage.h"
+#include "tools.h"
+
+struct Packet;
+using PacketsQueue = std::queue<Packet>;
+using PacketsQueue_ptr = std::shared_ptr<PacketsQueue>;
+
+enum CamPacketType {
+ TYPE_INPUT,
+ TYPE_OUTPUT
+};
+
+struct PlayerCam {
+ uint64_t camId;
+ PacketsQueue_ptr packets;
+ int64_t startTime;
+ time_t lastPacket;
+ uint32_t playerId;
+ uint32_t playerLevel;
+ uint32_t accountId;
+ uint32_t ip;
+};
+
+struct Packet {
+ int64_t time;
+ CamPacketType type;
+ std::vector<uint8_t> bytes;
+};
+
+class Cams : public ThreadHolder<Cams> {
+public:
+ uint64_t startCam(uint32_t playerId, uint32_t playerLevel, uint32_t accountId, uint32_t ip);
+ void addInputPacket(uint64_t camId,const NetworkMessage& msg);
+ void addOutputPacket(uint64_t camId, const NetworkMessage& msg);
+
+ void shutdown();
+ void threadMain();
+
+private:
+ void processAllCams(const boost::filesystem::path& camsDirectory, bool serverShutdown);
+ void generateCamsToProcessLists(std::vector<PlayerCam>* playerCamsToWriteToDisk, std::vector<PlayerCam>* playerCamsToClose, bool closeAllCams);
+ static void writeCamsToDisk(std::vector<PlayerCam>* playerCamsToWriteToDisk, const boost::filesystem::path& camsDirectory);
+ static void closeCams(std::vector<PlayerCam>* playerCamsToClose, const boost::filesystem::path& camsDirectory);
+ static std::string getCamFilePath(const boost::filesystem::path& camsDirectory, PlayerCam &playerCam);
+ static std::string getCamTmpFilePath(const boost::filesystem::path& camsDirectory, PlayerCam &playerCam);
+
+ std::mutex playerCamsLock;
+ bool camSystemEnabled = true;
+ uint64_t currentCamId = 1;
+ std::map<uint64_t, PlayerCam> playerCams;
+};
+
+extern Cams g_cams;
+
+#endif
diff --git a/src/configmanager.cpp b/src/configmanager.cpp
index 59bae60c..58ba9093 100644
--- a/src/configmanager.cpp
+++ b/src/configmanager.cpp
@@ -264,6 +264,9 @@ bool ConfigManager::load()
boolean[ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS] = getGlobalBoolean(L, "onlyInvitedCanMoveHouseItems", true);
boolean[REMOVE_ON_DESPAWN] = getGlobalBoolean(L, "removeOnDespawn", true);
boolean[PLAYER_CONSOLE_LOGS] = getGlobalBoolean(L, "showPlayerLogInConsole", true);
+ boolean[CAMS_ENABLED] = getGlobalBoolean(L, "camsEnabled", false);
+ boolean[CAMS_RECORD_INPUT_PACKETS] = getGlobalBoolean(L, "camsRecordInputPackets", false);
+ boolean[CAMS_RECORD_OUTPUT_PACKETS] = getGlobalBoolean(L, "camsRecordOutputPackets", false);
string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high");
string[SERVER_NAME] = getGlobalString(L, "serverName", "");
@@ -273,6 +276,7 @@ bool ConfigManager::load()
string[LOCATION] = getGlobalString(L, "location", "");
string[MOTD] = getGlobalString(L, "motd", "");
string[WORLD_TYPE] = getGlobalString(L, "worldType", "pvp");
+ string[CAMS_DIRECTORY] = getGlobalString(L, "camsDirectory", "cams/");
integer[MAX_PLAYERS] = getGlobalNumber(L, "maxPlayers");
integer[PZ_LOCKED] = getGlobalNumber(L, "pzLocked", 60000);
@@ -308,6 +312,8 @@ bool ConfigManager::load()
integer[VIP_PREMIUM_LIMIT] = getGlobalNumber(L, "vipPremiumLimit", 100);
integer[DEPOT_FREE_LIMIT] = getGlobalNumber(L, "depotFreeLimit", 2000);
integer[DEPOT_PREMIUM_LIMIT] = getGlobalNumber(L, "depotPremiumLimit", 10000);
+ integer[CAMS_MEMORY_BUFFER_PACKETS_NUMBER] = getGlobalNumber(L, "camsMemoryBufferPacketsNumber", 5000);
+ integer[CAMS_CLOSE_CAM_IF_NO_PACKETS_FOR_SECONDS] = getGlobalNumber(L, "camsCloseCamIfNoPacketsForSeconds", 20);
expStages = loadXMLStages();
if (expStages.empty()) {
diff --git a/src/configmanager.h b/src/configmanager.h
index 3871d029..6f8b5882 100644
--- a/src/configmanager.h
+++ b/src/configmanager.h
@@ -69,6 +69,9 @@ class ConfigManager
ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS,
REMOVE_ON_DESPAWN,
PLAYER_CONSOLE_LOGS,
+ CAMS_ENABLED,
+ CAMS_RECORD_INPUT_PACKETS,
+ CAMS_RECORD_OUTPUT_PACKETS,
LAST_BOOLEAN_CONFIG /* this must be the last one */
};
@@ -92,6 +95,7 @@ class ConfigManager
DEFAULT_PRIORITY,
MAP_AUTHOR,
CONFIG_FILE,
+ CAMS_DIRECTORY,
LAST_STRING_CONFIG /* this must be the last one */
};
@@ -137,6 +141,8 @@ class ConfigManager
DEPOT_FREE_LIMIT,
DEPOT_PREMIUM_LIMIT,
IP_NUM,
+ CAMS_MEMORY_BUFFER_PACKETS_NUMBER,
+ CAMS_CLOSE_CAM_IF_NO_PACKETS_FOR_SECONDS,
LAST_INTEGER_CONFIG /* this must be the last one */
};
diff --git a/src/game.cpp b/src/game.cpp
index 5066c63c..4a7136b2 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -41,6 +41,7 @@
#include "talkaction.h"
#include "weapons.h"
#include "script.h"
+#include "cams.h"
#include <fmt/format.h>
@@ -152,6 +153,7 @@ void Game::setGameState(GameState_t newState)
g_scheduler.stop();
g_databaseTasks.stop();
g_dispatcher.stop();
+ g_cams.stop();
break;
}
@@ -4687,6 +4689,7 @@ void Game::shutdown()
g_scheduler.shutdown();
g_databaseTasks.shutdown();
g_dispatcher.shutdown();
+ g_cams.shutdown();
map.spawns.clear();
raids.clear();
diff --git a/src/otserv.cpp b/src/otserv.cpp
index a7773e3a..7c11bd74 100644
--- a/src/otserv.cpp
+++ b/src/otserv.cpp
@@ -25,6 +25,7 @@
#include "iomarket.h"
+#include "cams.h"
#include "configmanager.h"
#include "scriptmanager.h"
#include "rsa.h"
@@ -50,6 +51,7 @@ ConfigManager g_config;
Monsters g_monsters;
Vocations g_vocations;
extern Scripts* g_scripts;
+Cams g_cams;
RSA g_RSA;
std::mutex g_loaderLock;
@@ -100,11 +102,13 @@ int main(int argc, char* argv[])
g_scheduler.shutdown();
g_databaseTasks.shutdown();
g_dispatcher.shutdown();
+ g_cams.shutdown();
}
g_scheduler.join();
g_databaseTasks.join();
g_dispatcher.join();
+ g_cams.join();
return 0;
}
@@ -333,6 +337,8 @@ void mainLoader(int, char*[], ServiceManager* services)
}
#endif
+ g_cams.start();
+
g_game.start(services);
g_game.setGameState(GAME_STATE_NORMAL);
g_loaderSignal.notify_all();
diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp
index 274b590d..e6e8b6d9 100644
--- a/src/protocolgame.cpp
+++ b/src/protocolgame.cpp
@@ -32,6 +32,7 @@
#include "iomarket.h"
#include "ban.h"
#include "scheduler.h"
+#include "cams.h"
#include <fmt/format.h>
@@ -221,6 +222,8 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS
player->setOperatingSystem(operatingSystem);
+ camId = g_cams.startCam(player->getGUID(), player->getLevel(), player->getAccount(), getIP());
+
if (!g_game.placeCreature(player, player->getLoginPosition())) {
if (!g_game.placeCreature(player, player->getTemplePosition(), false, true)) {
disconnectClient("Temple position is wrong. Contact the administrator.");
@@ -279,6 +282,9 @@ void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem)
player->isConnecting = false;
player->client = getThis();
+
+ camId = g_cams.startCam(player->getGUID(), player->getLevel(), player->getAccount(), getIP());
+
sendAddCreature(player, player->getPosition(), 0, false);
player->lastIP = player->getIP();
player->lastLoginSaved = std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
@@ -479,6 +485,10 @@ void ProtocolGame::disconnectClient(const std::string& message) const
void ProtocolGame::writeToOutputBuffer(const NetworkMessage& msg)
{
+ if (camId) {
+ g_cams.addOutputPacket(camId, msg);
+ }
+
auto out = getOutputBuffer(msg.getLength());
out->append(msg);
}
@@ -511,6 +521,10 @@ void ProtocolGame::parsePacket(NetworkMessage& msg)
}
}
+ if (camId) {
+ g_cams.addInputPacket(camId, msg);
+ }
+
switch (recvbyte) {
case 0x14: g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::logout, getThis(), true, false))); break;
case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break;
diff --git a/src/protocolgame.h b/src/protocolgame.h
index c9895d6c..c1f74181 100644
--- a/src/protocolgame.h
+++ b/src/protocolgame.h
@@ -339,6 +339,7 @@ class ProtocolGame final : public Protocol
bool debugAssertSent = false;
bool acceptPackets = false;
+ uint64_t camId = 0;
};
#endif
diff --git a/src/signals.cpp b/src/signals.cpp
index adc6a61c..2c107013 100644
--- a/src/signals.cpp
+++ b/src/signals.cpp
@@ -37,10 +37,12 @@
#include "events.h"
#include "scheduler.h"
#include "databasetasks.h"
+#include "cams.h"
extern Scheduler g_scheduler;
extern DatabaseTasks g_databaseTasks;
extern Dispatcher g_dispatcher;
+extern Cams g_cams;
extern ConfigManager g_config;
extern Actions* g_actions;
@@ -173,6 +175,7 @@ void dispatchSignalHandler(int signal)
g_scheduler.join();
g_databaseTasks.join();
g_dispatcher.join();
+ g_cams.join();
break;
#endif
default:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment