feat: Cam system by kondra (#1116)

This commit is contained in:
InnerCircleTFS 2025-03-16 17:20:00 -03:00 committed by GitHub
parent ea464b491c
commit 4694714921
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1046 additions and 4 deletions

View file

@ -468,6 +468,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu
- Placeholder
- UIGraph
- keybinds
- Cam system
## <a name="themobileproject"><img height="32" src="https://raw.githubusercontent.com/github/explore/80688e429a7d4ef2fca1e82350fe8e3517d3494d/topics/android/android.png" alt="Android"> The Mobile Project </a>
The Mobile Project

View file

@ -710,8 +710,9 @@ g_game = {}
---@param characterName string
---@param authenticatorToken string
---@param sessionKey string
---@param recordTo string
function g_game.loginWorld(account, password, worldName, worldHost, worldPort, characterName, authenticatorToken,
sessionKey)
sessionKey, recordTo)
end
function g_game.cancelLogin() end

4
records/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*
!.gitignore
!README.md
!test1098.cam

28
records/README.md Normal file
View file

@ -0,0 +1,28 @@
# Record On Client
`modules/client_entergame/characterlist.lua`
https://github.com/mehah/otclient/blob/5bf0e87ff4393d300c02e9185f18e8cea9f52a90/modules/client_entergame/characterlist.lua#L48
Add an argument to the function `g_game.loginWorld` , which represents the name and extension.
Ex: `os.time() .. '.cam'`
```diff
- g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey)
+ g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey, os.time() .. '.cam')
```
# Record On TFS by gesior
1) Add these changes https://github.com/gesior/tmp-cams-system
2) the server will create the .cam file
3) move the created file from the tfs/records folder to the OTC/records folder
# Play Record
In terminal :
```lua
g_game.setClientVersion(1098)
g_game.setProtocolVersion(g_game.getClientProtocolVersion(1098))
```
```lua
g_game.playRecord("test1098.cam")
EnterGame.hide()
```

641
records/test1098.cam Normal file

File diff suppressed because one or more lines are too long

View file

@ -300,6 +300,8 @@ set(SOURCE_FILES
framework/util/crypt.cpp
framework/proxy/proxy.cpp
framework/proxy/proxy_client.cpp
framework/net/packet_player.cpp
framework/net/packet_recorder.cpp
client/animatedtext.cpp
client/animator.cpp

View file

@ -34,6 +34,9 @@
#include "framework/core/graphicalapplication.h"
#include "tile.h"
#include <framework/net/packet_player.h>
#include <framework/net/packet_recorder.h>
Game g_game;
void Game::init()
@ -590,7 +593,7 @@ void Game::processWalkCancel(const Otc::Direction direction)
m_localPlayer->cancelWalk(direction);
}
void Game::loginWorld(const std::string_view account, const std::string_view password, const std::string_view worldName, const std::string_view worldHost, const int worldPort, const std::string_view characterName, const std::string_view authenticatorToken, const std::string_view sessionKey)
void Game::loginWorld(const std::string_view account, const std::string_view password, const std::string_view worldName, const std::string_view worldHost, const int worldPort, const std::string_view characterName, const std::string_view authenticatorToken, const std::string_view sessionKey, const std::string_view& recordTo)
{
if (m_protocolGame || isOnline())
throw Exception("Unable to login into a world while already online or logging.");
@ -606,11 +609,38 @@ void Game::loginWorld(const std::string_view account, const std::string_view pas
m_localPlayer->setName(characterName);
m_protocolGame = std::make_shared<ProtocolGame>();
if (!recordTo.empty()) {
m_protocolGame->setRecorder(std::make_shared<PacketRecorder>(recordTo));
}
m_protocolGame->login(account, password, worldHost, static_cast<uint16_t>(worldPort), characterName, authenticatorToken, sessionKey);
m_characterName = characterName;
m_worldName = worldName;
}
void Game::playRecord(const std::string_view& file)
{
if (m_protocolGame || isOnline())
throw Exception("Unable to login into a world while already online or logging.");
if (m_protocolVersion == 0)
throw Exception("Must set a valid game protocol version before logging.");
auto packetPlayer = std::make_shared<PacketPlayer>(file);
if (!packetPlayer)
throw Exception("Invalid record file.");
// reset the new game state
resetGameStates();
m_localPlayer = std::make_shared<LocalPlayer>();
m_localPlayer->setName("Player");
m_protocolGame = std::make_shared<ProtocolGame>();
m_protocolGame->playRecord(packetPlayer);
m_characterName = "Player";
m_worldName = "Record";
}
void Game::cancelLogin()
{
enableBotCall();

View file

@ -545,7 +545,8 @@ protected:
public:
// login related
void loginWorld(std::string_view account, std::string_view password, std::string_view worldName, std::string_view worldHost, int worldPort, std::string_view characterName, std::string_view authenticatorToken, std::string_view sessionKey);
void loginWorld(std::string_view account, std::string_view password, std::string_view worldName, std::string_view worldHost, int worldPort, std::string_view characterName, std::string_view authenticatorToken, std::string_view sessionKey, const std::string_view& recordTo);
void playRecord(const std::string_view& file);
void cancelLogin();
void forceLogout();
void safeLogout();

View file

@ -225,6 +225,7 @@ void Client::registerLuaFunctions()
g_lua.registerSingletonClass("g_game");
g_lua.bindSingletonFunction("g_game", "loginWorld", &Game::loginWorld, &g_game);
g_lua.bindSingletonFunction("g_game", "playRecord", &Game::playRecord, &g_game);
g_lua.bindSingletonFunction("g_game", "cancelLogin", &Game::cancelLogin, &g_game);
g_lua.bindSingletonFunction("g_game", "forceLogout", &Game::forceLogout, &g_game);
g_lua.bindSingletonFunction("g_game", "safeLogout", &Game::safeLogout, &g_game);

View file

@ -24,6 +24,9 @@
#include "framework/net/inputmessage.h"
#include "game.h"
#include <framework/net/packet_player.h>
#include <framework/net/packet_recorder.h>
void ProtocolGame::login(const std::string_view accountName, const std::string_view accountPassword, const std::string_view host, uint16_t port,
const std::string_view characterName, const std::string_view authenticatorToken, const std::string_view sessionKey)
{

View file

@ -375,6 +375,7 @@ private:
bool m_gameInitialized{ false };
bool m_mapKnown{ false };
bool m_firstRecv{ true };
bool m_record {false};
std::string m_accountName;
std::string m_accountPassword;

View file

@ -35,6 +35,8 @@ class Connection;
class Protocol;
class ProtocolHttp;
class Server;
class PacketPlayer;
class PacketRecorder;
using InputMessagePtr = std::shared_ptr<InputMessage>;
using OutputMessagePtr = std::shared_ptr<OutputMessage>;
@ -46,3 +48,5 @@ using ConnectionPtr = std::shared_ptr<Connection>;
using ProtocolPtr = std::shared_ptr<Protocol>;
using ProtocolHttpPtr = std::shared_ptr<ProtocolHttp>;
using ServerPtr = std::shared_ptr<Server>;
using PacketPlayerPtr = std::shared_ptr<PacketPlayer>;
using PacketRecorderPtr = std::shared_ptr<PacketRecorder>;

View file

@ -37,6 +37,7 @@ public:
void setBuffer(const std::string& buffer);
std::string_view getBuffer() { return std::string_view{ (char*)m_buffer + m_headerPos, m_messageSize }; }
std::string getBodyBuffer() { return std::string((char*)m_buffer + MAX_HEADER_SIZE, m_messageSize - getHeaderSize()); }
void skipBytes(const uint16_t bytes) { m_readPos += bytes; }
void setReadPos(const uint16_t readPos) { m_readPos = readPos; }

View file

@ -66,6 +66,7 @@ protected:
void writeMessageSize();
friend class Protocol;
friend class PacketPlayer;
private:
bool canWrite(int bytes) const;

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) 2010-2024 OTClient <https://github.com/edubart/otclient>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <framework/global.h>
#include <framework/core/clock.h>
#include <filesystem>
#include "packet_player.h"
PacketPlayer::~PacketPlayer()
{
if (m_event)
m_event->cancel();
}
PacketPlayer::PacketPlayer(const std::string_view& file)
{
static uint32_t sessionId = 1;
#ifdef ANDROID
std::ifstream f(std::string("records/") + file);
#else
std::ifstream f(std::filesystem::path("records") / file);
#endif
if (!f.is_open())
return;
std::string type, packetHex;
ticks_t time;
while (f >> type >> time >> packetHex) {
// Convert hex string to binary data manually
std::string packetStr;
for (size_t i = 0; i < packetHex.length(); i += 2) {
std::string byteString = packetHex.substr(i, 2);
char byte = (char)strtol(byteString.c_str(), nullptr, 16);
packetStr.push_back(byte);
}
auto packet = std::make_shared<std::vector<uint8_t>>(packetStr.begin(), packetStr.end());
if (type == "<") {
m_input.push_back(std::make_pair(time, packet));
} else if (type == ">") {
m_output.push_back(std::make_pair(time, packet));
}
}
}
void PacketPlayer::start(std::function<void(std::shared_ptr<std::vector<uint8_t>>)> recvCallback,
std::function<void(std::error_code)> disconnectCallback)
{
m_start = g_clock.millis();
m_recvCallback = recvCallback;
m_disconnectCallback = disconnectCallback;
m_event = g_dispatcher.scheduleEvent(std::bind(&PacketPlayer::process, this), 50);
}
void PacketPlayer::stop()
{
if (m_event)
m_event->cancel();
m_event = nullptr;
}
void PacketPlayer::onOutputPacket(const OutputMessagePtr& packet)
{
if (packet->getBuffer()[0] == 0x14) { // logout
m_disconnectCallback(asio::error::eof);
stop();
}
}
void PacketPlayer::process()
{
ticks_t nextPacket = 1;
while (!m_input.empty()) {
auto& packet = m_input.front();
nextPacket = (packet.first + m_start) - g_clock.millis();
if (nextPacket > 1)
break;
m_recvCallback(packet.second);
m_input.pop_front();
}
if (!m_input.empty() && nextPacket > 1) {
m_event = g_dispatcher.scheduleEvent(std::bind(&PacketPlayer::process, this), nextPacket);
} else {
m_disconnectCallback(asio::error::eof);
stop();
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2010-2024 OTClient <https://github.com/edubart/otclient>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <deque>
#include <framework/core/eventdispatcher.h>
#include <framework/net/outputmessage.h>
class PacketPlayer : public LuaObject {
public:
PacketPlayer(const std::string_view& file);
virtual ~PacketPlayer();
void start(std::function<void(std::shared_ptr<std::vector<uint8_t>>)> recvCallback, std::function<void(std::error_code)> disconnectCallback);
void stop();
void onOutputPacket(const OutputMessagePtr& packet);
private:
void process();
ticks_t m_start;
ScheduledEventPtr m_event;
std::deque<std::pair<ticks_t, std::shared_ptr<std::vector<uint8_t>>>> m_input;
std::deque<std::pair<ticks_t, std::shared_ptr<std::vector<uint8_t>>>> m_output;
std::function<void(std::shared_ptr<std::vector<uint8_t>>)> m_recvCallback;
std::function<void(std::error_code)> m_disconnectCallback;
};

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2010-2024 OTClient <https://github.com/edubart/otclient>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <framework/global.h>
#include <framework/core/clock.h>
#include <framework/core/resourcemanager.h>
#include "packet_recorder.h"
PacketRecorder::PacketRecorder(const std::string_view& file)
{
m_start = g_clock.millis();
#ifdef ANDROID
g_resources.makeDir("records");
m_stream = std::ofstream(std::string("records/") + file);
#else
std::error_code ec;
std::filesystem::create_directory("records", ec);
m_stream = std::ofstream(std::filesystem::path("records") / file);
#endif
}
PacketRecorder::~PacketRecorder()
{
}
void PacketRecorder::addInputPacket(const InputMessagePtr& packet)
{
m_stream << "< " << (g_clock.millis() - m_start) << " ";
for (auto& buffer : packet->getBodyBuffer()) {
m_stream << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(uint8_t)buffer;
}
m_stream << std::dec << "\n";
}
void PacketRecorder::addOutputPacket(const OutputMessagePtr& packet)
{
if (m_firstOutput) {
// skip packet with login and password
m_firstOutput = false;
return;
}
m_stream << "> " << (g_clock.millis() - m_start) << " ";
for (auto& buffer : packet->getBuffer()) {
m_stream << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(uint8_t)buffer;
}
m_stream << std::dec << "\n";
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2010-2024 OTClient <https://github.com/edubart/otclient>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <framework/net/inputmessage.h>
#include <framework/net/outputmessage.h>
class PacketRecorder : public LuaObject {
public:
PacketRecorder(const std::string_view& file);
virtual ~PacketRecorder();
void addInputPacket(const InputMessagePtr& packet);
void addOutputPacket(const OutputMessagePtr& packet);
private:
ticks_t m_start;
std::ofstream m_stream;
bool m_firstOutput = true;
};

View file

@ -33,6 +33,8 @@
#else
#include "connection.h"
#endif
#include <framework/net/packet_player.h>
#include <framework/net/packet_recorder.h>
extern asio::io_service g_ioService;
@ -109,6 +111,16 @@ bool Protocol::isConnecting()
void Protocol::send(const OutputMessagePtr& outputMessage)
{
if (m_player) {
m_player->onOutputPacket(outputMessage);
return;
}
if (m_recorder) {
m_recorder->addOutputPacket(outputMessage);
}
// encrypt
if (m_xteaEncryptionEnabled)
xteaEncrypt(outputMessage);
@ -227,6 +239,9 @@ void Protocol::internalRecvData(const uint8_t* buffer, const uint16_t size)
m_inputMessage->setMessageSize(m_inputMessage->getHeaderSize() + totalSize);
}
if (m_recorder) {
m_recorder->addInputPacket(m_inputMessage);
}
onRecv(m_inputMessage);
}
@ -355,4 +370,39 @@ void Protocol::onLocalDisconnected(std::error_code ec)
m_disconnected = true;
onError(ec);
});
}
}
void Protocol::onPlayerPacket(const std::shared_ptr<std::vector<uint8_t>>& packet)
{
if (m_disconnected)
return;
auto self(asProtocol());
post(g_ioService, [&, packet] {
if (m_disconnected)
return;
m_inputMessage->reset();
m_inputMessage->setHeaderSize(0);
m_inputMessage->fillBuffer(packet->data(), packet->size());
m_inputMessage->setMessageSize(packet->size());
onRecv(m_inputMessage);
});
}
void Protocol::playRecord(PacketPlayerPtr player)
{
m_disconnected = false;
m_player = player;
m_player->start([capture0 = asProtocol()](auto&& PH1) {
capture0->onPlayerPacket(std::forward<decltype(PH1)>(PH1));
},
[capture0 = asProtocol()](auto&& PH1) {
capture0->onLocalDisconnected(std::forward<decltype(PH1)>(PH1));
});
return onConnect();
}
void Protocol::setRecorder(PacketRecorderPtr recorder)
{
m_recorder = recorder;
}

View file

@ -46,6 +46,9 @@ public:
#endif
void disconnect();
void setRecorder(PacketRecorderPtr recorder);
void playRecord(PacketPlayerPtr player);
bool isConnected();
bool isConnecting();
ticks_t getElapsedTicksSinceLastRead() const { return m_connection ? m_connection->getElapsedTicksSinceLastRead() : -1; }
@ -80,6 +83,7 @@ protected:
virtual void onSend() {};
void onProxyPacket(const std::shared_ptr<std::vector<uint8_t>>& packet);
void onPlayerPacket(const std::shared_ptr<std::vector<uint8_t>>& packet);
void onLocalDisconnected(std::error_code ec);
bool m_disconnected = false;
uint32_t m_proxy = 0;
@ -87,6 +91,8 @@ protected:
std::array<uint32_t, 4> m_xteaKey{};
uint32_t m_packetNumber{ 0 };
PacketPlayerPtr m_player;
PacketRecorderPtr m_recorder;
private:
void internalRecvHeader(const uint8_t* buffer, uint16_t size);
void internalRecvData(const uint8_t* buffer, uint16_t size);

View file

@ -397,6 +397,8 @@
<ClCompile Include="..\src\framework\net\httplogin.cpp" />
<ClCompile Include="..\src\framework\net\inputmessage.cpp" />
<ClCompile Include="..\src\framework\net\outputmessage.cpp" />
<ClCompile Include="..\src\framework\net\packet_player.cpp" />
<ClCompile Include="..\src\framework\net\packet_recorder.cpp" />
<ClCompile Include="..\src\framework\net\protocol.cpp" />
<ClCompile Include="..\src\framework\net\protocolhttp.cpp" />
<ClCompile Include="..\src\framework\net\server.cpp" />
@ -563,6 +565,8 @@
<ClInclude Include="..\src\framework\net\httplogin.h" />
<ClInclude Include="..\src\framework\net\inputmessage.h" />
<ClInclude Include="..\src\framework\net\outputmessage.h" />
<ClInclude Include="..\src\framework\net\packet_player.h" />
<ClInclude Include="..\src\framework\net\packet_recorder.h" />
<ClInclude Include="..\src\framework\net\protocol.h" />
<ClInclude Include="..\src\framework\net\protocolhttp.h" />
<ClInclude Include="..\src\framework\net\server.h" />