Skip to content

Commit 4694714

Browse files
feat: Cam system by kondra (#1116)
1 parent ea464b4 commit 4694714

21 files changed

+1046
-4
lines changed

README.md

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

meta.lua

+2-1
Original file line numberDiff line numberDiff line change
@@ -710,8 +710,9 @@ g_game = {}
710710
---@param characterName string
711711
---@param authenticatorToken string
712712
---@param sessionKey string
713+
---@param recordTo string
713714
function g_game.loginWorld(account, password, worldName, worldHost, worldPort, characterName, authenticatorToken,
714-
sessionKey)
715+
sessionKey, recordTo)
715716
end
716717

717718
function g_game.cancelLogin() end

records/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*
2+
!.gitignore
3+
!README.md
4+
!test1098.cam

records/README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Record On Client
2+
3+
`modules/client_entergame/characterlist.lua`
4+
https://github.com/mehah/otclient/blob/5bf0e87ff4393d300c02e9185f18e8cea9f52a90/modules/client_entergame/characterlist.lua#L48
5+
Add an argument to the function `g_game.loginWorld` , which represents the name and extension.
6+
7+
Ex: `os.time() .. '.cam'`
8+
```diff
9+
- g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey)
10+
+ g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey, os.time() .. '.cam')
11+
```
12+
13+
# Record On TFS by gesior
14+
1) Add these changes https://github.com/gesior/tmp-cams-system
15+
2) the server will create the .cam file
16+
3) move the created file from the tfs/records folder to the OTC/records folder
17+
18+
# Play Record
19+
In terminal :
20+
```lua
21+
g_game.setClientVersion(1098)
22+
g_game.setProtocolVersion(g_game.getClientProtocolVersion(1098))
23+
```
24+
25+
```lua
26+
g_game.playRecord("test1098.cam")
27+
EnterGame.hide()
28+
```

records/test1098.cam

+641
Large diffs are not rendered by default.

src/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ set(SOURCE_FILES
300300
framework/util/crypt.cpp
301301
framework/proxy/proxy.cpp
302302
framework/proxy/proxy_client.cpp
303+
framework/net/packet_player.cpp
304+
framework/net/packet_recorder.cpp
303305

304306
client/animatedtext.cpp
305307
client/animator.cpp

src/client/game.cpp

+31-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
#include "framework/core/graphicalapplication.h"
3535
#include "tile.h"
3636

37+
#include <framework/net/packet_player.h>
38+
#include <framework/net/packet_recorder.h>
39+
3740
Game g_game;
3841

3942
void Game::init()
@@ -590,7 +593,7 @@ void Game::processWalkCancel(const Otc::Direction direction)
590593
m_localPlayer->cancelWalk(direction);
591594
}
592595

593-
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)
596+
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)
594597
{
595598
if (m_protocolGame || isOnline())
596599
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
606609
m_localPlayer->setName(characterName);
607610

608611
m_protocolGame = std::make_shared<ProtocolGame>();
612+
if (!recordTo.empty()) {
613+
m_protocolGame->setRecorder(std::make_shared<PacketRecorder>(recordTo));
614+
}
609615
m_protocolGame->login(account, password, worldHost, static_cast<uint16_t>(worldPort), characterName, authenticatorToken, sessionKey);
610616
m_characterName = characterName;
611617
m_worldName = worldName;
612618
}
613619

620+
void Game::playRecord(const std::string_view& file)
621+
{
622+
if (m_protocolGame || isOnline())
623+
throw Exception("Unable to login into a world while already online or logging.");
624+
625+
if (m_protocolVersion == 0)
626+
throw Exception("Must set a valid game protocol version before logging.");
627+
628+
auto packetPlayer = std::make_shared<PacketPlayer>(file);
629+
if (!packetPlayer)
630+
throw Exception("Invalid record file.");
631+
632+
// reset the new game state
633+
resetGameStates();
634+
635+
m_localPlayer = std::make_shared<LocalPlayer>();
636+
m_localPlayer->setName("Player");
637+
638+
m_protocolGame = std::make_shared<ProtocolGame>();
639+
m_protocolGame->playRecord(packetPlayer);
640+
m_characterName = "Player";
641+
m_worldName = "Record";
642+
}
643+
614644
void Game::cancelLogin()
615645
{
616646
enableBotCall();

src/client/game.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,8 @@ class Game
545545

546546
public:
547547
// login related
548-
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);
548+
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);
549+
void playRecord(const std::string_view& file);
549550
void cancelLogin();
550551
void forceLogout();
551552
void safeLogout();

src/client/luafunctions.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ void Client::registerLuaFunctions()
225225

226226
g_lua.registerSingletonClass("g_game");
227227
g_lua.bindSingletonFunction("g_game", "loginWorld", &Game::loginWorld, &g_game);
228+
g_lua.bindSingletonFunction("g_game", "playRecord", &Game::playRecord, &g_game);
228229
g_lua.bindSingletonFunction("g_game", "cancelLogin", &Game::cancelLogin, &g_game);
229230
g_lua.bindSingletonFunction("g_game", "forceLogout", &Game::forceLogout, &g_game);
230231
g_lua.bindSingletonFunction("g_game", "safeLogout", &Game::safeLogout, &g_game);

src/client/protocolgame.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
#include "framework/net/inputmessage.h"
2525
#include "game.h"
2626

27+
#include <framework/net/packet_player.h>
28+
#include <framework/net/packet_recorder.h>
29+
2730
void ProtocolGame::login(const std::string_view accountName, const std::string_view accountPassword, const std::string_view host, uint16_t port,
2831
const std::string_view characterName, const std::string_view authenticatorToken, const std::string_view sessionKey)
2932
{

src/client/protocolgame.h

+1
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ class ProtocolGame final : public Protocol
375375
bool m_gameInitialized{ false };
376376
bool m_mapKnown{ false };
377377
bool m_firstRecv{ true };
378+
bool m_record {false};
378379

379380
std::string m_accountName;
380381
std::string m_accountPassword;

src/framework/net/declarations.h

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class Connection;
3535
class Protocol;
3636
class ProtocolHttp;
3737
class Server;
38+
class PacketPlayer;
39+
class PacketRecorder;
3840

3941
using InputMessagePtr = std::shared_ptr<InputMessage>;
4042
using OutputMessagePtr = std::shared_ptr<OutputMessage>;
@@ -46,3 +48,5 @@ using ConnectionPtr = std::shared_ptr<Connection>;
4648
using ProtocolPtr = std::shared_ptr<Protocol>;
4749
using ProtocolHttpPtr = std::shared_ptr<ProtocolHttp>;
4850
using ServerPtr = std::shared_ptr<Server>;
51+
using PacketPlayerPtr = std::shared_ptr<PacketPlayer>;
52+
using PacketRecorderPtr = std::shared_ptr<PacketRecorder>;

src/framework/net/inputmessage.h

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class InputMessage final : public LuaObject
3737

3838
void setBuffer(const std::string& buffer);
3939
std::string_view getBuffer() { return std::string_view{ (char*)m_buffer + m_headerPos, m_messageSize }; }
40+
std::string getBodyBuffer() { return std::string((char*)m_buffer + MAX_HEADER_SIZE, m_messageSize - getHeaderSize()); }
4041

4142
void skipBytes(const uint16_t bytes) { m_readPos += bytes; }
4243
void setReadPos(const uint16_t readPos) { m_readPos = readPos; }

src/framework/net/outputmessage.h

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class OutputMessage final : public LuaObject
6666
void writeMessageSize();
6767

6868
friend class Protocol;
69+
friend class PacketPlayer;
6970

7071
private:
7172
bool canWrite(int bytes) const;

src/framework/net/packet_player.cpp

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright (c) 2010-2024 OTClient <https://github.com/edubart/otclient>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*/
22+
23+
#include <framework/global.h>
24+
#include <framework/core/clock.h>
25+
#include <filesystem>
26+
27+
#include "packet_player.h"
28+
29+
PacketPlayer::~PacketPlayer()
30+
{
31+
if (m_event)
32+
m_event->cancel();
33+
}
34+
35+
PacketPlayer::PacketPlayer(const std::string_view& file)
36+
{
37+
static uint32_t sessionId = 1;
38+
#ifdef ANDROID
39+
std::ifstream f(std::string("records/") + file);
40+
#else
41+
std::ifstream f(std::filesystem::path("records") / file);
42+
#endif
43+
if (!f.is_open())
44+
return;
45+
std::string type, packetHex;
46+
ticks_t time;
47+
while (f >> type >> time >> packetHex) {
48+
// Convert hex string to binary data manually
49+
std::string packetStr;
50+
for (size_t i = 0; i < packetHex.length(); i += 2) {
51+
std::string byteString = packetHex.substr(i, 2);
52+
char byte = (char)strtol(byteString.c_str(), nullptr, 16);
53+
packetStr.push_back(byte);
54+
}
55+
auto packet = std::make_shared<std::vector<uint8_t>>(packetStr.begin(), packetStr.end());
56+
if (type == "<") {
57+
m_input.push_back(std::make_pair(time, packet));
58+
} else if (type == ">") {
59+
m_output.push_back(std::make_pair(time, packet));
60+
}
61+
}
62+
}
63+
64+
void PacketPlayer::start(std::function<void(std::shared_ptr<std::vector<uint8_t>>)> recvCallback,
65+
std::function<void(std::error_code)> disconnectCallback)
66+
{
67+
m_start = g_clock.millis();
68+
m_recvCallback = recvCallback;
69+
m_disconnectCallback = disconnectCallback;
70+
m_event = g_dispatcher.scheduleEvent(std::bind(&PacketPlayer::process, this), 50);
71+
}
72+
73+
void PacketPlayer::stop()
74+
{
75+
if (m_event)
76+
m_event->cancel();
77+
m_event = nullptr;
78+
}
79+
80+
void PacketPlayer::onOutputPacket(const OutputMessagePtr& packet)
81+
{
82+
if (packet->getBuffer()[0] == 0x14) { // logout
83+
m_disconnectCallback(asio::error::eof);
84+
stop();
85+
}
86+
}
87+
88+
void PacketPlayer::process()
89+
{
90+
ticks_t nextPacket = 1;
91+
while (!m_input.empty()) {
92+
auto& packet = m_input.front();
93+
nextPacket = (packet.first + m_start) - g_clock.millis();
94+
if (nextPacket > 1)
95+
break;
96+
m_recvCallback(packet.second);
97+
m_input.pop_front();
98+
}
99+
100+
if (!m_input.empty() && nextPacket > 1) {
101+
m_event = g_dispatcher.scheduleEvent(std::bind(&PacketPlayer::process, this), nextPacket);
102+
} else {
103+
m_disconnectCallback(asio::error::eof);
104+
stop();
105+
}
106+
}

src/framework/net/packet_player.h

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) 2010-2024 OTClient <https://github.com/edubart/otclient>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*/
22+
23+
#pragma once
24+
25+
#include <deque>
26+
#include <framework/core/eventdispatcher.h>
27+
#include <framework/net/outputmessage.h>
28+
29+
class PacketPlayer : public LuaObject {
30+
public:
31+
PacketPlayer(const std::string_view& file);
32+
virtual ~PacketPlayer();
33+
34+
void start(std::function<void(std::shared_ptr<std::vector<uint8_t>>)> recvCallback, std::function<void(std::error_code)> disconnectCallback);
35+
void stop();
36+
37+
void onOutputPacket(const OutputMessagePtr& packet);
38+
39+
private:
40+
void process();
41+
42+
ticks_t m_start;
43+
ScheduledEventPtr m_event;
44+
std::deque<std::pair<ticks_t, std::shared_ptr<std::vector<uint8_t>>>> m_input;
45+
std::deque<std::pair<ticks_t, std::shared_ptr<std::vector<uint8_t>>>> m_output;
46+
std::function<void(std::shared_ptr<std::vector<uint8_t>>)> m_recvCallback;
47+
std::function<void(std::error_code)> m_disconnectCallback;
48+
};

0 commit comments

Comments
 (0)