Skip to content

Commit d82d7eb

Browse files
committed
Initial import
1 parent f671a85 commit d82d7eb

17 files changed

+212244
-0
lines changed

ArkCrossServerChat.sln

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.26403.7
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ArkCrossServerChat", "ArkCrossServerChat\ArkCrossServerChat.vcxproj", "{6E3CB8BD-966B-4CAA-8CF6-D8DD3499DC35}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|x64 = Debug|x64
11+
Debug|x86 = Debug|x86
12+
Release|x64 = Release|x64
13+
Release|x86 = Release|x86
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{6E3CB8BD-966B-4CAA-8CF6-D8DD3499DC35}.Debug|x64.ActiveCfg = Debug|x64
17+
{6E3CB8BD-966B-4CAA-8CF6-D8DD3499DC35}.Debug|x64.Build.0 = Debug|x64
18+
{6E3CB8BD-966B-4CAA-8CF6-D8DD3499DC35}.Debug|x86.ActiveCfg = Debug|Win32
19+
{6E3CB8BD-966B-4CAA-8CF6-D8DD3499DC35}.Debug|x86.Build.0 = Debug|Win32
20+
{6E3CB8BD-966B-4CAA-8CF6-D8DD3499DC35}.Release|x64.ActiveCfg = Release|x64
21+
{6E3CB8BD-966B-4CAA-8CF6-D8DD3499DC35}.Release|x64.Build.0 = Release|x64
22+
{6E3CB8BD-966B-4CAA-8CF6-D8DD3499DC35}.Release|x86.ActiveCfg = Release|Win32
23+
{6E3CB8BD-966B-4CAA-8CF6-D8DD3499DC35}.Release|x86.Build.0 = Release|Win32
24+
EndGlobalSection
25+
GlobalSection(SolutionProperties) = preSolution
26+
HideSolutionNode = FALSE
27+
EndGlobalSection
28+
EndGlobal
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
#include <fstream>
2+
#include <chrono>
3+
#include "ArkCrossServerChat.h"
4+
#include "Tools.h"
5+
6+
#pragma comment(lib, "ArkApi.lib")
7+
8+
nlohmann::json json;
9+
sqlite::database* db;
10+
HANDLE ghManualResetEvent;
11+
HANDLE ghThread;
12+
bool bExit = false;
13+
long long lastRowId = 0;
14+
wchar_t* manualResetEventName;
15+
std::chrono::time_point<std::chrono::system_clock> NextCleanupTime;
16+
17+
namespace
18+
{
19+
DECLARE_HOOK(AShooterPlayerController_ServerSendChatMessage_Impl, void, AShooterPlayerController*, FString*, EChatSendMode::Type);
20+
DECLARE_HOOK(UShooterCheatManager_ServerChat, void, UShooterCheatManager*, FString*);
21+
22+
void CleanupTimer();
23+
void LoadConfig();
24+
void Update();
25+
DWORD WINAPI ThreadProc(LPVOID lpParam);
26+
27+
void Init()
28+
{
29+
if (sqlite3_threadsafe() == 0) {
30+
Tools::Log("sqlite is not threadsafe");
31+
throw;
32+
}
33+
34+
LoadConfig();
35+
std::string serverKey = json["ServerKey"];
36+
if (serverKey.empty()) {
37+
Tools::Log("ServerKey must be set in config.json");
38+
throw;
39+
}
40+
41+
std::string clusterKey = json["ClusterKey"];
42+
if (clusterKey.empty()) {
43+
Tools::Log("ClusterKey must be set in config.json");
44+
throw;
45+
}
46+
std::wstring tmp = std::wstring(L"ArkCrossServerChat_").append(Tools::FromUTF8(clusterKey));
47+
manualResetEventName = Tools::CopyWString(tmp);
48+
49+
std::string databasePath = json["DatabasePath"];
50+
if (databasePath.empty()) {
51+
Tools::Log("DatabasePath must be set in config.json");
52+
throw;
53+
}
54+
//todo: mkdir here
55+
db = new sqlite::database(databasePath);
56+
57+
*db << u"create table if not exists Messages ("
58+
"Id integer primary key autoincrement not null,"
59+
"At integer default 0,"
60+
"ServerKey text default '',"
61+
"SteamId integer default 0,"
62+
"PlayerName text default '',"
63+
"CharacterName text default '',"
64+
"TribeName text default '',"
65+
"Message text default '',"
66+
"Type integer default 0,"
67+
"Rcon integer default 0"
68+
");";
69+
70+
//todo: would be better to do this once the server is started and world is up and running
71+
*db << "SELECT Id FROM Messages ORDER BY Id DESC LIMIT 1;" >> [&](long long id) {
72+
lastRowId = id;
73+
};
74+
75+
ghManualResetEvent = CreateEvent(NULL, TRUE, FALSE, manualResetEventName);
76+
77+
if (ghManualResetEvent == NULL) {
78+
Tools::Log("failed to create sync event");
79+
throw;
80+
}
81+
82+
DWORD dwThreadID;
83+
ghThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &dwThreadID);
84+
85+
if (ghThread == NULL)
86+
{
87+
Tools::Log("failed to create thread");
88+
throw;
89+
}
90+
91+
NextCleanupTime = std::chrono::system_clock::now() + std::chrono::seconds(300); //init with cleanup in 5 min
92+
93+
Ark::SetHook("AShooterPlayerController", "ServerSendChatMessage_Implementation", &Hook_AShooterPlayerController_ServerSendChatMessage_Impl, reinterpret_cast<LPVOID*>(&AShooterPlayerController_ServerSendChatMessage_Impl_original));
94+
Ark::SetHook("UShooterCheatManager", "ServerChat", &Hook_UShooterCheatManager_ServerChat, reinterpret_cast<LPVOID*>(&UShooterCheatManager_ServerChat_original));
95+
96+
Ark::AddOnTimerCallback(&CleanupTimer);
97+
}
98+
99+
void CleanupTimer()
100+
{
101+
auto now = std::chrono::system_clock::now();
102+
auto diff = std::chrono::duration_cast<std::chrono::seconds>(NextCleanupTime - now);
103+
104+
if (diff.count() <= 0)
105+
{
106+
NextCleanupTime = now + std::chrono::seconds(3600);
107+
108+
try
109+
{
110+
*db << u"DELETE FROM Messages WHERE At <= ?;" << (std::chrono::system_clock::now() - std::chrono::seconds(3600 * 24 * 7)).time_since_epoch().count();
111+
}
112+
catch (sqlite::sqlite_exception& e)
113+
{
114+
std::cout << "Unexpected DB error " << e.what() << std::endl;
115+
}
116+
}
117+
}
118+
119+
void LoadConfig()
120+
{
121+
std::ifstream file(Tools::GetCurrentDir() + "/BeyondApi/Plugins/ArkCrossServerChat/config.json");
122+
if (!file.is_open())
123+
{
124+
Tools::Log("Could not open file config.json");
125+
throw;
126+
}
127+
128+
file >> json;
129+
file.close();
130+
}
131+
132+
DWORD WINAPI ThreadProc(LPVOID lpParam)
133+
{
134+
UNREFERENCED_PARAMETER(lpParam);
135+
136+
while (!bExit) {
137+
//todo: we could use a timeout here
138+
DWORD dwWaitResult = WaitForSingleObject(ghManualResetEvent, INFINITE);
139+
140+
if (bExit) break;
141+
142+
143+
switch (dwWaitResult)
144+
{
145+
case WAIT_OBJECT_0:
146+
case WAIT_TIMEOUT:
147+
if (Ark::GetWorld()) {
148+
Update();
149+
}
150+
break;
151+
default:
152+
Sleep(1000); //todo: ?
153+
}
154+
}
155+
156+
return 0;
157+
}
158+
159+
void Update()
160+
{
161+
try
162+
{
163+
std::string serverKey = json["ServerKey"];
164+
*db << "SELECT Id,At,ServerKey,SteamId,PlayerName,CharacterName,TribeName,Message,Type,Rcon FROM Messages WHERE Id > ? ORDER BY Id ASC;" << lastRowId >>
165+
[&](long long id, long long at, std::string msgServerKey, long long steamId, std::string playerName, std::u16string characterName, std::string tribeName, std::u16string message, int type, int rcon) {
166+
if (msgServerKey.compare(serverKey) != 0) {
167+
//send chat message to users
168+
if (rcon == 0) {
169+
Tools::SendChatMessageToAll(Tools::FromUTF16(characterName), Tools::FromUTF8(playerName), Tools::FromUTF8(tribeName), Tools::FromUTF16(message));
170+
}
171+
else {
172+
Tools::SendRconChatMessageToAll(Tools::FromUTF16(message));
173+
}
174+
}
175+
176+
lastRowId = id;
177+
};
178+
}
179+
catch (sqlite::sqlite_exception& e)
180+
{
181+
std::cout << "Unexpected DB error " << e.what() << std::endl;
182+
}
183+
}
184+
185+
void _cdecl Hook_AShooterPlayerController_ServerSendChatMessage_Impl(AShooterPlayerController* _AShooterPlayerController, FString* Message, EChatSendMode::Type Mode)
186+
{
187+
long double lastChatMessageTime = 0;
188+
wchar_t* msg = NULL;
189+
if (_AShooterPlayerController && Message && Mode == EChatSendMode::Type::GlobalChat) { //todo: for now only relay global chat
190+
lastChatMessageTime = _AShooterPlayerController->GetLastChatMessageTimeField();
191+
msg = Tools::CopyWString(std::wstring(**Message));
192+
}
193+
194+
AShooterPlayerController_ServerSendChatMessage_Impl_original(_AShooterPlayerController, Message, Mode);
195+
196+
if (_AShooterPlayerController && Message && Mode == EChatSendMode::Type::GlobalChat) { //todo: for now only relay global chat
197+
if (_AShooterPlayerController->GetLastChatMessageTimeField() <= lastChatMessageTime) return; //skip if the message was not sent (due to spam filter etc.)
198+
199+
__int64 steamId = Tools::GetSteamId(_AShooterPlayerController);
200+
std::string playerName = Tools::GetPlayerName(_AShooterPlayerController);
201+
std::wstring characterName = Tools::GetPlayerCharacterName(_AShooterPlayerController);
202+
std::string tribeName = Tools::GetTribeName(_AShooterPlayerController);
203+
204+
//checking for admin badge is tricky (going by manual offset means no automatic updates)
205+
//[admin] ([_AShooterPlayerController+0x0930] & 4 != 0)
206+
//bSuppressAdminIcon (0x0CD6)
207+
//Another way: _AShooterPlayerController->GetPlayerCharacter, AShooterCharacter->bIsServerAdmin
208+
209+
//markup in the message is replaced if the player is not an admin
210+
/*while (FString::ReplaceInline((FString *)&v106, L" ", L" ", IgnoreCase))
211+
while (FString::ReplaceInline((FString *)&v106, L"<img", &word_142316594, IgnoreCase))
212+
while (FString::ReplaceInline((FString *)&v106, L"< img", &word_142316484, IgnoreCase))
213+
while (FString::ReplaceInline((FString *)&v106, L"Chat.Image", &word_1423164CC, IgnoreCase))
214+
while (FString::ReplaceInline((FString *)&v106, L"src=", &word_14231661C, IgnoreCase))
215+
while (FString::ReplaceInline((FString *)&v106, L"src =", &word_142316884, IgnoreCase))
216+
while (FString::ReplaceInline((FString *)&v106, L"path=", &word_1423165BC, IgnoreCase))
217+
while (FString::ReplaceInline((FString *)&v106, L"\n", &word_1423165FC, IgnoreCase))
218+
while (FString::ReplaceInline((FString *)&v106, L"\\n", &word_1423169BC, IgnoreCase))
219+
while (FString::ReplaceInline((FString *)&v106, L"\\n", &word_1423169F4, IgnoreCase))
220+
while (FString::ReplaceInline((FString *)&v106, L"<RichColor", &word_1423168CC, IgnoreCase))
221+
while (FString::ReplaceInline((FString *)&v106, L"< RichColor", &word_142316934, IgnoreCase))*/
222+
223+
try
224+
{
225+
std::string serverKey = json["ServerKey"];
226+
*db << u"INSERT INTO Messages (At,ServerKey,SteamId,PlayerName,CharacterName,TribeName,Message,Type) VALUES (?,?,?,?,?,?,?,?);"
227+
<< std::chrono::system_clock::now().time_since_epoch().count() << serverKey << steamId << playerName << Tools::ToUTF16(characterName) << tribeName << Tools::ToUTF16(msg) << (int)Mode;
228+
229+
//notify other servers
230+
PulseEvent(ghManualResetEvent);
231+
}
232+
catch (sqlite::sqlite_exception& e)
233+
{
234+
std::cout << "ServerSendChatMessage_Impl() Unexpected DB error " << e.what() << std::endl;
235+
}
236+
}
237+
238+
if (msg) {
239+
delete[] msg;
240+
}
241+
}
242+
243+
void _cdecl Hook_UShooterCheatManager_ServerChat(UShooterCheatManager* _UShooterCheatManager, FString* MessageText)
244+
{
245+
if (MessageText) {
246+
try
247+
{
248+
std::string serverKey = json["ServerKey"];
249+
*db << u"INSERT INTO Messages (At,ServerKey,Message,Type,Rcon) VALUES (?,?,?,?,?);"
250+
<< std::chrono::system_clock::now().time_since_epoch().count() << serverKey << Tools::ToUTF16(**MessageText) << -1 << 1;
251+
252+
//notify other servers
253+
PulseEvent(ghManualResetEvent);
254+
}
255+
catch (sqlite::sqlite_exception& e)
256+
{
257+
std::cout << "UShooterCheatManager_ServerChat() Unexpected DB error " << e.what() << std::endl;
258+
}
259+
}
260+
261+
UShooterCheatManager_ServerChat_original(_UShooterCheatManager, MessageText);
262+
}
263+
}
264+
265+
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
266+
{
267+
switch (ul_reason_for_call)
268+
{
269+
case DLL_PROCESS_ATTACH:
270+
Init();
271+
break;
272+
case DLL_THREAD_ATTACH:
273+
case DLL_THREAD_DETACH:
274+
break;
275+
case DLL_PROCESS_DETACH:
276+
bExit = true;
277+
278+
CloseHandle(ghManualResetEvent);
279+
//CloseHandle(ghThread);
280+
281+
if (db) {
282+
delete db;
283+
db = NULL;
284+
}
285+
if (manualResetEventName) {
286+
delete[] manualResetEventName;
287+
}
288+
break;
289+
}
290+
return TRUE;
291+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#pragma once
2+
3+
#include "hdr/sqlite_modern_cpp.h"
4+
#include "json.hpp"
5+
#include "API/Base.h"
6+
7+
extern nlohmann::json json;

0 commit comments

Comments
 (0)