--- 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(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(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 g_loaderUniqueLock(g_loaderLock); @@ -269,6 +272,9 @@ services->add(static_cast(g_config.getNumber(ConfigManager::GAME_PORT))); services->add(static_cast(g_config.getNumber(ConfigManager::LOGIN_PORT))); + // Replay + services->add(static_cast(g_config.getNumber(ConfigManager::REPLAY_PORT))); + // OT protocols services->add(static_cast(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 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(player->getID()); + msg.add(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(0x00); + msg.add(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(i)); + if (item) { + msg.addByte(0x78); + msg.addByte(static_cast(i)); + msg.addItem(item); + } else { + msg.addByte(0x79); + msg.addByte(static_cast(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(time(nullptr) + (player->premiumDays * 86400)); + } else { + msg.addByte(0); + msg.add(0); + } + msg.addByte(player->getVocation()->getClientId()); + msg.add(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(g_config.getNumber(ConfigManager::REPLAY_PORT)); + output->addByte(0); + + const ReplayList& replays = g_replays.getReplays(); + uint8_t size = std::min(std::numeric_limits::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(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(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