Skip to content

Instantly share code, notes, and snippets.

@lyuz1n
Created August 24, 2019 19:20
Show Gist options
  • Save lyuz1n/62bd5e635cb1d0fade73acc7cfa063b4 to your computer and use it in GitHub Desktop.
Save lyuz1n/62bd5e635cb1d0fade73acc7cfa063b4 to your computer and use it in GitHub Desktop.
Cam System 10.98
#include "otpch.h"
#include "protocolreplay.h"
#include "outputmessage.h"
#include "configmanager.h"
#include "game.h"
#include "ban.h"
#include "scheduler.h"
extern ConfigManager g_config;
extern Game g_game;
extern ZReplays g_replays;
void ProtocolReplay::release()
{
OutputMessagePool::getInstance().removeProtocolFromAutosend(shared_from_this());
Protocol::release();
}
void ProtocolReplay::onRecvFirstMessage(NetworkMessage& msg)
{
if (g_game.getGameState() == GAME_STATE_SHUTDOWN) {
disconnect();
return;
}
OperatingSystem_t operatingSystem = static_cast<OperatingSystem_t>(msg.get<uint16_t>());
uint16_t version = msg.get<uint16_t>();
msg.skipBytes(7); // U32 client version, U8 client type, U16 dat revision
if (!Protocol::RSA_decrypt(msg)) {
disconnect();
return;
}
xtea::key key;
key[0] = msg.get<uint32_t>();
key[1] = msg.get<uint32_t>();
key[2] = msg.get<uint32_t>();
key[3] = msg.get<uint32_t>();
enableXTEAEncryption();
setXTEAKey(std::move(key));
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
NetworkMessage opcodeMessage;
opcodeMessage.addByte(0x32);
opcodeMessage.addByte(0x00);
opcodeMessage.add<uint16_t>(0x00);
writeToOutputBuffer(opcodeMessage);
}
msg.skipBytes(1); // gamemaster flag
std::string sessionKey = msg.getString();
std::string characterName = msg.getString();
uint32_t timeStamp = msg.get<uint32_t>();
uint8_t randNumber = msg.getByte();
if (challengeTimestamp != timeStamp || challengeRandom != randNumber) {
disconnect();
return;
}
g_scheduler.addEvent(createSchedulerTask(0, std::bind(&ProtocolReplay::watchRecording, getThis(), characterName)));
}
void ProtocolReplay::onConnect()
{
auto output = OutputMessagePool::getOutputMessage();
static std::random_device rd;
static std::ranlux24 generator(rd());
static std::uniform_int_distribution<uint16_t> randNumber(0x00, 0xFF);
// Skip checksum
output->skipBytes(sizeof(uint32_t));
// Packet length & type
output->add<uint16_t>(0x0006);
output->addByte(0x1F);
// Add timestamp & random number
challengeTimestamp = static_cast<uint32_t>(time(nullptr));
output->add<uint32_t>(challengeTimestamp);
challengeRandom = randNumber(generator);
output->addByte(challengeRandom);
// Go back and write checksum
output->skipBytes(-12);
output->add<uint32_t>(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8));
send(output);
}
void ProtocolReplay::disconnectClient(const std::string& message) const
{
auto output = OutputMessagePool::getOutputMessage();
output->addByte(0x14);
output->addString(message);
send(output);
disconnect();
}
void ProtocolReplay::writeToOutputBuffer(const NetworkMessage& msg)
{
auto out = getOutputBuffer(msg.getLength());
out->append(msg);
}
void ProtocolReplay::watchRecording(const std::string& replayName)
{
for (const auto& replay : g_replays.getReplays()) {
if (replay.title == replayName) {
data = replay.packets;
break;
}
}
if (data.empty()) {
disconnectClient("This replay doesn't exists.");
return;
}
playRecording();
OutputMessagePool::getInstance().addProtocolToAutosend(shared_from_this());
}
void ProtocolReplay::playRecording()
{
if (data.empty()) {
releaseEvent();
return;
}
const NetworkMessage& msg = data.front().second;
time_t interval = data.front().first;
data.pop();
if (data.empty()) {
releaseEvent();
return;
}
interval = data.front().first - interval;
eventId = g_scheduler.addEvent(createSchedulerTask(interval, std::bind(&ProtocolReplay::playRecording, getThis())));
writeToOutputBuffer(msg);
}
void ProtocolReplay::parsePacket(NetworkMessage& msg)
{
if (msg.getLength() <= 0) {
return;
}
uint8_t recvbyte = msg.getByte();
switch (recvbyte) {
case 0x14: releaseEvent(); break;
default:
NetworkMessage msg;
msg.addByte(0xB5);
msg.addByte(2);
writeToOutputBuffer(msg);
}
if (msg.isOverrun()) {
disconnect();
}
}
void ProtocolReplay::releaseEvent()
{
if (eventId) {
g_scheduler.stopEvent(eventId);
eventId = 0;
}
disconnect();
}
#ifndef __PROTOCOLREPLAY__
#define __PROTOCOLREPLAY__
#include "protocol.h"
#include "z_replay.h"
class ProtocolReplay;
using ProtocolReplay_ptr = std::shared_ptr<ProtocolReplay>;
class ProtocolReplay final : public Protocol
{
public:
enum {server_sends_first = true};
enum {protocol_identifier = 0};
enum {use_checksum = true};
static const char* protocol_name() {
return "replay protocol";
}
explicit ProtocolReplay(Connection_ptr connection) : Protocol(connection) {}
private:
ProtocolReplay_ptr getThis() {
return std::static_pointer_cast<ProtocolReplay>(shared_from_this());
}
void writeToOutputBuffer(const NetworkMessage& msg);
void release() override;
void releaseEvent();
void disconnectClient(const std::string& message) const;
void parsePacket(NetworkMessage& msg) override;
void onRecvFirstMessage(NetworkMessage& msg) override;
void onConnect() override;
void watchRecording(const std::string& replayName);
void playRecording();
PacketQueue data;
uint32_t eventId = 0;
uint32_t challengeTimestamp = 0;
uint8_t challengeRandom = 0;
};
#endif
--- C:\Users\halls\Desktop\forgottenserver\src\configmanager.cpp 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\configmanager.cpp 2019-08-24 16:13:17.053000000 -0300
@@ -116,7 +116,7 @@
integer[GAME_PORT] = getGlobalNumber(L, "gameProtocolPort", 7172);
integer[LOGIN_PORT] = getGlobalNumber(L, "loginProtocolPort", 7171);
integer[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171);
-
+ integer[REPLAY_PORT] = getGlobalNumber(L, "replayProtocolPort", 7174);
integer[MARKET_OFFER_DURATION] = getGlobalNumber(L, "marketOfferDuration", 30 * 24 * 60 * 60);
}
--- C:\Users\halls\Desktop\forgottenserver\src\configmanager.h 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\configmanager.h 2019-08-22 20:05:20.817000000 -0300
@@ -94,6 +94,7 @@
GAME_PORT,
LOGIN_PORT,
STATUS_PORT,
+ REPLAY_PORT,
STAIRHOP_DELAY,
MARKET_OFFER_DURATION,
CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES,
--- C:\Users\halls\Desktop\forgottenserver\src\luascript.cpp 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\luascript.cpp 2019-08-24 15:52:59.672000000 -0300
@@ -53,6 +53,7 @@
extern GlobalEvents* g_globalEvents;
extern Scripts* g_scripts;
extern Weapons* g_weapons;
+extern ZReplays g_replays;
ScriptEnvironment::DBResultMap ScriptEnvironment::tempResults;
uint32_t ScriptEnvironment::lastResultId = 0;
@@ -2429,6 +2430,10 @@
registerMethod("Player", "hasSecureMode", LuaScriptInterface::luaPlayerHasSecureMode);
registerMethod("Player", "getFightMode", LuaScriptInterface::luaPlayerGetFightMode);
+ // Replay
+ registerMethod("Player", "startReplay", LuaScriptInterface::luaPlayerStartReplay);
+ registerMethod("Player", "stopReplay", LuaScriptInterface::luaPlayerStopReplay);
+
// Monster
registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate);
registerMetaMethod("Monster", "__eq", LuaScriptInterface::luaUserdataCompare);
@@ -16159,3 +16164,39 @@
luaL_unref(luaState, LUA_REGISTRYINDEX, parameter);
}
}
+
+int LuaScriptInterface::luaPlayerStartReplay(lua_State* L)
+{
+ // player:startReplay()
+ Player* player = getUserdata<Player>(L, 1);
+ if (player) {
+ if (player->replayData.recording) {
+ pushBoolean(L, false);
+ } else {
+ player->startReplay();
+ pushBoolean(L, true);
+ }
+ } else {
+ lua_pushnil(L);
+ }
+
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerStopReplay(lua_State* L)
+{
+ // player:stopReplay()
+ Player* player = getUserdata<Player>(L, 1);
+ if (player) {
+ if (!player->replayData.recording) {
+ pushBoolean(L, false);
+ } else {
+ g_replays.saveReplay(player->replayData);
+ pushBoolean(L, true);
+ }
+ } else {
+ lua_pushnil(L);
+ }
+
+ return 1;
+}
--- C:\Users\halls\Desktop\forgottenserver\src\luascript.h 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\luascript.h 2019-08-22 18:20:21.985000000 -0300
@@ -992,6 +992,10 @@
static int luaPlayerHasSecureMode(lua_State* L);
static int luaPlayerGetFightMode(lua_State* L);
+ // Replay
+ static int luaPlayerStartReplay(lua_State* L);
+ static int luaPlayerStopReplay(lua_State* L);
+
// Monster
static int luaMonsterCreate(lua_State* L);
--- C:\Users\halls\Desktop\forgottenserver\src\otserv.cpp 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\otserv.cpp 2019-08-24 15:56:38.867000000 -0300
@@ -31,6 +31,7 @@
#include "protocolold.h"
#include "protocollogin.h"
#include "protocolstatus.h"
+#include "protocolreplay.h"
#include "databasemanager.h"
#include "scheduler.h"
#include "databasetasks.h"
@@ -48,6 +49,8 @@
extern Scripts* g_scripts;
RSA g_RSA;
+ZReplays g_replays;
+
std::mutex g_loaderLock;
std::condition_variable g_loaderSignal;
std::unique_lock<std::mutex> g_loaderUniqueLock(g_loaderLock);
@@ -269,6 +272,9 @@
services->add<ProtocolGame>(static_cast<uint16_t>(g_config.getNumber(ConfigManager::GAME_PORT)));
services->add<ProtocolLogin>(static_cast<uint16_t>(g_config.getNumber(ConfigManager::LOGIN_PORT)));
+ // Replay
+ services->add<ProtocolReplay>(static_cast<uint16_t>(g_config.getNumber(ConfigManager::REPLAY_PORT)));
+
// OT protocols
services->add<ProtocolStatus>(static_cast<uint16_t>(g_config.getNumber(ConfigManager::STATUS_PORT)));
--- C:\Users\halls\Desktop\forgottenserver\src\player.h 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\player.h 2019-08-22 21:33:45.034000000 -0300
@@ -36,6 +36,7 @@
#include "groups.h"
#include "town.h"
#include "mounts.h"
+#include "z_replay.h"
class House;
class NetworkMessage;
@@ -1142,6 +1143,12 @@
void forgetInstantSpell(const std::string& spellName);
bool hasLearnedInstantSpell(const std::string& spellName) const;
+ void startReplay() {
+ if (client) {
+ client->startReplay();
+ }
+ }
+
private:
std::forward_list<Condition*> getMuteConditions() const;
@@ -1215,6 +1222,8 @@
Position loginPosition;
Position lastWalkthroughPosition;
+ ZReplayData replayData;
+
time_t lastLoginSaved = 0;
time_t lastLogout = 0;
--- C:\Users\halls\Desktop\forgottenserver\src\protocolgame.cpp 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\protocolgame.cpp 2019-08-22 22:46:21.443000000 -0300
@@ -40,11 +40,16 @@
extern Actions actions;
extern CreatureEvents* g_creatureEvents;
extern Chat* g_chat;
+extern ZReplays g_replays;
void ProtocolGame::release()
{
//dispatcher thread
if (player && player->client == shared_from_this()) {
+ if (player->replayData.recording) {
+ g_replays.saveReplay(player->replayData);
+ }
+
player->client.reset();
player->decrementReferenceCounter();
player = nullptr;
@@ -385,6 +390,11 @@
{
auto out = getOutputBuffer(msg.getLength());
out->append(msg);
+
+ // add packets to data
+ if (player->replayData.recording) {
+ player->replayData.packets.push({ OTSYS_TIME(), msg });
+ }
}
void ProtocolGame::parsePacket(NetworkMessage& msg)
@@ -3089,3 +3099,71 @@
// process additional opcodes via lua script event
addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer);
}
+
+void ProtocolGame::startReplay()
+{
+ if (g_replays.getReplays().size() >= Z_MAX_REPLAYS) {
+ return;
+ }
+
+ knownCreatureSet.clear();
+ player->replayData.recording = true;
+
+ // Let's go to simulate a sendCreature
+ NetworkMessage msg;
+ msg.addByte(0x17);
+ msg.add<uint32_t>(player->getID());
+ msg.add<uint16_t>(0x32);
+ msg.addDouble(Creature::speedA, 3);
+ msg.addDouble(Creature::speedB, 3);
+ msg.addDouble(Creature::speedC, 3);
+ msg.addByte(0x00);
+ msg.addByte(0x00);
+ msg.addByte(0x00);
+ msg.add<uint16_t>(0x00);
+ msg.add<uint16_t>(25);
+ msg.addByte(0x0A);
+ msg.addByte(0x0F);
+
+ const Position& pos = player->getPosition();
+ msg.addByte(0x64);
+ msg.addPosition(player->getPosition());
+ GetMapDescription(pos.x - 8, pos.y - 6, pos.z, 18, 14, msg);
+
+ for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
+ const Item* item = player->getInventoryItem(static_cast<slots_t>(i));
+ if (item) {
+ msg.addByte(0x78);
+ msg.addByte(static_cast<slots_t>(i));
+ msg.addItem(item);
+ } else {
+ msg.addByte(0x79);
+ msg.addByte(static_cast<slots_t>(i));
+ }
+ }
+
+ AddPlayerStats(msg);
+ AddPlayerSkills(msg);
+ AddWorldLight(msg, g_game.getWorldLightInfo());
+ if (canSee(player)) {
+ AddCreatureLight(msg, player);
+ }
+
+ sendVIPEntries();
+ msg.addByte(0x9F);
+ if (player->isPremium()) {
+ msg.addByte(1);
+ msg.add<uint32_t>(time(nullptr) + (player->premiumDays * 86400));
+ } else {
+ msg.addByte(0);
+ msg.add<uint32_t>(0);
+ }
+ msg.addByte(player->getVocation()->getClientId());
+ msg.add<uint16_t>(0xFF);
+ for (uint8_t spellId = 0x00; spellId < 0xFF; spellId++) {
+ msg.addByte(spellId);
+ }
+
+ player->sendIcons();
+ player->replayData.packets.push({ OTSYS_TIME(), msg });
+}
--- C:\Users\halls\Desktop\forgottenserver\src\protocolgame.h 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\protocolgame.h 2019-08-22 19:37:15.078000000 -0300
@@ -301,6 +301,8 @@
//otclient
void parseExtendedOpcode(NetworkMessage& msg);
+ void startReplay();
+
friend class Player;
// Helpers so we don't need to bind every time
--- C:\Users\halls\Desktop\forgottenserver\src\protocollogin.cpp 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\protocollogin.cpp 2019-08-22 22:08:18.459000000 -0300
@@ -31,6 +31,7 @@
extern ConfigManager g_config;
extern Game g_game;
+extern ZReplays g_replays;
void ProtocolLogin::disconnectClient(const std::string& message, uint16_t version)
{
@@ -115,6 +116,47 @@
disconnect();
}
+void ProtocolLogin::getReplays(const std::string& password)
+{
+ uint32_t ticks = time(nullptr) / AUTHENTICATOR_PERIOD;
+ auto output = OutputMessagePool::getOutputMessage();
+
+ const std::string& motd = g_config.getString(ConfigManager::MOTD);
+ if (!motd.empty()) {
+ output->addByte(0x14);
+
+ std::ostringstream ss;
+ ss << g_game.getMotdNum() << "\n" << motd;
+ output->addString(ss.str());
+ }
+
+ output->addByte(0x28);
+ output->addString(password + "\n" + std::to_string(ticks));
+
+ output->addByte(0x64);
+ output->addByte(1);
+
+ output->addByte(0);
+ output->addString(g_config.getString(ConfigManager::SERVER_NAME));
+ output->addString(g_config.getString(ConfigManager::IP));
+ output->add<uint16_t>(g_config.getNumber(ConfigManager::REPLAY_PORT));
+ output->addByte(0);
+
+ const ReplayList& replays = g_replays.getReplays();
+ uint8_t size = std::min<size_t>(std::numeric_limits<uint8_t>::max(), replays.size());
+ output->addByte(size);
+ for (const ZReplayData& replay : replays) {
+ output->addByte(0);
+ output->addString(replay.title);
+ }
+
+ output->addByte(0);
+ output->addByte(1);
+ output->add<uint32_t>(0);
+
+ send(output);
+ disconnect();
+}
void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
{
@@ -193,16 +235,21 @@
}
std::string accountName = msg.getString();
- if (accountName.empty()) {
- disconnectClient("Invalid account name.", version);
- return;
- }
+ std::string password = msg.getString();
+ bool replayList = false;
+ if (accountName == "" && password == "replaylist") {
+ replayList = true;
+ } else {
+ if (accountName.empty()) {
+ disconnectClient("Invalid account name.", version);
+ return;
+ }
- std::string password = msg.getString();
- if (password.empty()) {
- disconnectClient("Invalid password.", version);
- return;
- }
+ if (password.empty()) {
+ disconnectClient("Invalid password.", version);
+ return;
+ }
+ }
// read authenticator token and stay logged in flag from last 128 bytes
msg.skipBytes((msg.getLength() - 128) - msg.getBufferPosition());
@@ -214,5 +261,9 @@
std::string authToken = msg.getString();
auto thisPtr = std::static_pointer_cast<ProtocolLogin>(shared_from_this());
- g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountName, password, authToken, version)));
-}
+ if (!replayList) {
+ g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountName, password, authToken, version)));
+ } else {
+ g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getReplays, thisPtr, password)));
+ }
+}
--- C:\Users\halls\Desktop\forgottenserver\src\protocollogin.h 2019-08-19 12:16:13.000000000 -0300
+++ C:\Users\halls\Desktop\forgotten\src\protocollogin.h 2019-08-22 18:23:46.004000000 -0300
@@ -44,6 +44,7 @@
void disconnectClient(const std::string& message, uint16_t version);
void getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version);
+ void getReplays(const std::string& password);
};
#endif
function onSay(player, words, param)
if param == 'start' then
player:sendTextMessage(MESSAGE_INFO_DESCR, 'The recording has started.')
player:startReplay()
elseif param == 'stop' then
player:sendTextMessage(MESSAGE_INFO_DESCR, 'The recording has stoped.')
player:stopReplay()
else
player:sendCancelMessage('use start/stop.')
end
end
#include "otpch.h"
#include "player.h"
#include "z_replay.h"
void ZReplays::saveReplay(ZReplayData& replay)
{
replay.recording = false;
replay.title = formatDate(time(0));
ZReplayData copy = replay;
// clear player replay structure to be able to record again.
replay.title.clear();
PacketQueue().swap(replay.packets);
replayList.push_back(std::move(copy));
}
#ifndef __Z_REPLAY__
#define __Z_REPLAY__
#include "protocolgame.h"
using PacketQueue = std::queue<std::pair<time_t, NetworkMessage>>;
struct ZReplayData {
bool recording = false;
std::string title;
PacketQueue packets;
};
static constexpr uint8_t Z_MAX_REPLAYS = 5;
using ReplayList = std::vector<ZReplayData>;
class ZReplays
{
public:
const ReplayList& getReplays() { return replayList; }
void saveReplay(ZReplayData& replay);
private:
ReplayList replayList;
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment