mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2025-07-01 07:15:49 +00:00
Fix Console on Unix, adapt console behavior to that of a traditional
console, add Assert.h, add clang-format file with modified WebKit style
This commit is contained in:
parent
13e79e407c
commit
eead954bf9
5
.clang-format
Normal file
5
.clang-format
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
BasedOnStyle: WebKit
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
|
||||||
|
...
|
59
include/Assert.h
Normal file
59
include/Assert.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Author: lionkor
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
static const char* const ANSI_RESET = "\u001b[0m";
|
||||||
|
|
||||||
|
static const char* const ANSI_BLACK = "\u001b[30m";
|
||||||
|
static const char* const ANSI_RED = "\u001b[31m";
|
||||||
|
static const char* const ANSI_GREEN = "\u001b[32m";
|
||||||
|
static const char* const ANSI_YELLOW = "\u001b[33m";
|
||||||
|
static const char* const ANSI_BLUE = "\u001b[34m";
|
||||||
|
static const char* const ANSI_MAGENTA = "\u001b[35m";
|
||||||
|
static const char* const ANSI_CYAN = "\u001b[36m";
|
||||||
|
static const char* const ANSI_WHITE = "\u001b[37m";
|
||||||
|
|
||||||
|
static const char* const ANSI_BLACK_BOLD = "\u001b[30;1m";
|
||||||
|
static const char* const ANSI_RED_BOLD = "\u001b[31;1m";
|
||||||
|
static const char* const ANSI_GREEN_BOLD = "\u001b[32;1m";
|
||||||
|
static const char* const ANSI_YELLOW_BOLD = "\u001b[33;1m";
|
||||||
|
static const char* const ANSI_BLUE_BOLD = "\u001b[34;1m";
|
||||||
|
static const char* const ANSI_MAGENTA_BOLD = "\u001b[35;1m";
|
||||||
|
static const char* const ANSI_CYAN_BOLD = "\u001b[36;1m";
|
||||||
|
static const char* const ANSI_WHITE_BOLD = "\u001b[37;1m";
|
||||||
|
|
||||||
|
static const char* const ANSI_BOLD = "\u001b[1m";
|
||||||
|
static const char* const ANSI_UNDERLINE = "\u001b[4m";
|
||||||
|
|
||||||
|
inline void _assert(const char* file, const char* function, unsigned line,
|
||||||
|
const char* condition_string, bool result) {
|
||||||
|
if (!result) {
|
||||||
|
#if DEBUG
|
||||||
|
fprintf(stderr,
|
||||||
|
"%sASSERTION FAILED%s at %s%s:%u%s \n\t-> in %s%s%s, Line %u: \n\t\t-> "
|
||||||
|
"Failed Condition: %s%s%s\n",
|
||||||
|
ANSI_RED_BOLD, ANSI_RESET, ANSI_UNDERLINE, file, line, ANSI_RESET,
|
||||||
|
ANSI_BOLD, function, ANSI_RESET, line, ANSI_RED, condition_string,
|
||||||
|
ANSI_RESET);
|
||||||
|
fprintf(stderr, "%s... terminating with SIGABRT ...%s\n", ANSI_BOLD, ANSI_RESET);
|
||||||
|
abort();
|
||||||
|
#else
|
||||||
|
char buf[2048];
|
||||||
|
sprintf(buf,
|
||||||
|
"%s=> ASSERTION `%s` FAILED IN RELEASE BUILD%s%s -> IGNORING FAILED ASSERTION "
|
||||||
|
"& HOPING IT WON'T CRASH%s\n",
|
||||||
|
ANSI_RED_BOLD, condition_string, ANSI_RESET, ANSI_RED, ANSI_RESET);
|
||||||
|
error(buf);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef ASSERT
|
||||||
|
#define Assert(cond) _assert(__FILE__, __func__, __LINE__, #cond, (cond))
|
||||||
|
#endif // ASSERT
|
||||||
|
#define AssertNotReachable() _assert(__FILE__, __func__, __LINE__, "reached unreachable code", false)
|
@ -3,12 +3,13 @@
|
|||||||
///
|
///
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include "Assert.h"
|
||||||
class Client;
|
class Client;
|
||||||
void GParser(Client*c, const std::string&Packet);
|
void GParser(Client*c, const std::string&Packet);
|
||||||
class Buffer{
|
class Buffer{
|
||||||
public:
|
public:
|
||||||
void Handle(Client*c,const std::string& Data){
|
void Handle(Client*c,const std::string& Data){
|
||||||
if(c == nullptr)return;
|
Assert(c);
|
||||||
Buf += Data;
|
Buf += Data;
|
||||||
Manage(c);
|
Manage(c);
|
||||||
}
|
}
|
||||||
@ -18,6 +19,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
std::string Buf;
|
std::string Buf;
|
||||||
void Manage(Client*c){
|
void Manage(Client*c){
|
||||||
|
Assert(c);
|
||||||
if(!Buf.empty()){
|
if(!Buf.empty()){
|
||||||
std::string::size_type p;
|
std::string::size_type p;
|
||||||
if (Buf.at(0) == '\n'){
|
if (Buf.at(0) == '\n'){
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
#include <conio.h>
|
#include <conio.h>
|
||||||
#else // *nix
|
#else // *nix
|
||||||
typedef unsigned long DWORD, *PDWORD, *LPDWORD;
|
typedef unsigned long DWORD, *PDWORD, *LPDWORD;
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
#endif // __WIN32
|
#endif // __WIN32
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -20,6 +22,7 @@ std::string CInputBuff;
|
|||||||
std::mutex MLock;
|
std::mutex MLock;
|
||||||
Lua* LuaConsole;
|
Lua* LuaConsole;
|
||||||
void HandleInput(const std::string& cmd){
|
void HandleInput(const std::string& cmd){
|
||||||
|
std::cout << std::endl;
|
||||||
if (cmd == "exit") {
|
if (cmd == "exit") {
|
||||||
exit(0);
|
exit(0);
|
||||||
}else LuaConsole->Execute(cmd);
|
}else LuaConsole->Execute(cmd);
|
||||||
@ -28,13 +31,12 @@ void HandleInput(const std::string& cmd){
|
|||||||
void ProcessOut(){
|
void ProcessOut(){
|
||||||
static size_t len = 2;
|
static size_t len = 2;
|
||||||
if(QConsoleOut.empty() && len == CInputBuff.length())return;
|
if(QConsoleOut.empty() && len == CInputBuff.length())return;
|
||||||
printf("%c[2K\r", 27);
|
|
||||||
for(const std::string& msg : QConsoleOut)
|
for(const std::string& msg : QConsoleOut)
|
||||||
if(!msg.empty())std::cout << msg;
|
if(!msg.empty())std::cout << msg;
|
||||||
MLock.lock();
|
MLock.lock();
|
||||||
QConsoleOut.clear();
|
QConsoleOut.clear();
|
||||||
MLock.unlock();
|
MLock.unlock();
|
||||||
std::cout << "> " << CInputBuff;
|
std::cout << "> " << CInputBuff << std::flush;
|
||||||
len = CInputBuff.length();
|
len = CInputBuff.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +52,32 @@ void ConsoleOut(const std::string& msg){
|
|||||||
ProcessOut();
|
ProcessOut();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef __WIN32
|
||||||
|
static int _getch()
|
||||||
|
{
|
||||||
|
char buf = 0;
|
||||||
|
struct termios old;
|
||||||
|
fflush(stdout);
|
||||||
|
if(tcgetattr(0, &old) < 0)
|
||||||
|
perror("tcsetattr()");
|
||||||
|
old.c_lflag &= ~unsigned(ICANON);
|
||||||
|
old.c_lflag &= ~unsigned(ECHO);
|
||||||
|
old.c_cc[VMIN] = 1;
|
||||||
|
old.c_cc[VTIME] = 0;
|
||||||
|
if(tcsetattr(0, TCSANOW, &old) < 0)
|
||||||
|
perror("tcsetattr ICANON");
|
||||||
|
if(read(0, &buf, 1) < 0)
|
||||||
|
perror("read()");
|
||||||
|
old.c_lflag |= ICANON;
|
||||||
|
old.c_lflag |= ECHO;
|
||||||
|
if(tcsetattr(0, TCSADRAIN, &old) < 0)
|
||||||
|
perror("tcsetattr ~ICANON");
|
||||||
|
// no echo printf("%c\n", buf);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
#endif // __WIN32
|
||||||
|
|
||||||
void SetupConsole(){
|
void SetupConsole(){
|
||||||
#ifdef __WIN32
|
#ifdef __WIN32
|
||||||
DWORD outMode = 0;
|
DWORD outMode = 0;
|
||||||
@ -71,19 +99,28 @@ void SetupConsole(){
|
|||||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||||
exit(GetLastError());
|
exit(GetLastError());
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
#endif // __WIN32
|
#endif // __WIN32
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void ReadCin(){
|
[[noreturn]] void ReadCin(){
|
||||||
while (true){
|
while (true){
|
||||||
int In = getchar();
|
int In = _getch();
|
||||||
if (In == 13) {
|
if (In == 13 || In == '\n') {
|
||||||
if(!CInputBuff.empty()) {
|
if(!CInputBuff.empty()) {
|
||||||
HandleInput(CInputBuff);
|
HandleInput(CInputBuff);
|
||||||
CInputBuff.clear();
|
CInputBuff.clear();
|
||||||
}
|
}
|
||||||
}else if(In == 8){
|
}else if(In == 8){
|
||||||
if(!CInputBuff.empty())CInputBuff.pop_back();
|
if(!CInputBuff.empty())CInputBuff.pop_back();
|
||||||
}else CInputBuff += char(In);
|
} else if (In == 4) {
|
||||||
|
CInputBuff = "exit";
|
||||||
|
HandleInput(CInputBuff);
|
||||||
|
CInputBuff.clear();
|
||||||
|
}else {
|
||||||
|
printf("%c[2K\r", 27);
|
||||||
|
CInputBuff += char(In);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void ConsoleInit(){
|
void ConsoleInit(){
|
||||||
|
@ -163,7 +163,7 @@ void GlobalParser(Client*c, const std::string& Pack){
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (Code) {
|
switch (Code) {
|
||||||
case 'P':
|
case 'P': // initial connection
|
||||||
Respond(c, Sec("P") + std::to_string(c->GetID()),true);
|
Respond(c, Sec("P") + std::to_string(c->GetID()),true);
|
||||||
SyncClient(c);
|
SyncClient(c);
|
||||||
return;
|
return;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
///
|
///
|
||||||
/// Created by Anonymous275 on 8/1/2020
|
/// Created by Anonymous275 on 8/1/2020
|
||||||
///
|
///
|
||||||
#include "Security/Enc.h"
|
|
||||||
#include "Client.hpp"
|
#include "Client.hpp"
|
||||||
#include "Settings.h"
|
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
|
#include "Security/Enc.h"
|
||||||
|
#include "Settings.h"
|
||||||
#include "UnixCompat.h"
|
#include "UnixCompat.h"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
@ -13,22 +13,25 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#endif // __linux
|
#endif // __linux
|
||||||
|
|
||||||
void STCPSend(Client*c,std::string Data){
|
void STCPSend(Client* c, std::string Data) {
|
||||||
if(c == nullptr)return;
|
if (c == nullptr)
|
||||||
|
return;
|
||||||
ssize_t BytesSent = send(c->GetTCPSock(), Data.c_str(), size_t(Data.size()), 0);
|
ssize_t BytesSent = send(c->GetTCPSock(), Data.c_str(), size_t(Data.size()), 0);
|
||||||
Data.clear();
|
Data.clear();
|
||||||
if (BytesSent == 0){
|
if (BytesSent == 0) {
|
||||||
if(c->GetStatus() > -1)c->SetStatus(-1);
|
if (c->GetStatus() > -1)
|
||||||
}else if (BytesSent < 0) {
|
c->SetStatus(-1);
|
||||||
if(c->GetStatus() > -1)c->SetStatus(-1);
|
} else if (BytesSent < 0) {
|
||||||
|
if (c->GetStatus() > -1)
|
||||||
|
c->SetStatus(-1);
|
||||||
closesocket(c->GetTCPSock());
|
closesocket(c->GetTCPSock());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void SendFile(Client*c,const std::string&Name){
|
void SendFile(Client* c, const std::string& Name) {
|
||||||
info(c->GetName()+Sec(" requesting : ")+Name.substr(Name.find_last_of('/')));
|
info(c->GetName() + Sec(" requesting : ") + Name.substr(Name.find_last_of('/')));
|
||||||
struct stat Info{};
|
struct stat Info {};
|
||||||
if(stat(Name.c_str(), &Info) != 0){
|
if (stat(Name.c_str(), &Info) != 0) {
|
||||||
STCPSend(c,Sec("Cannot Open"));
|
STCPSend(c, Sec("Cannot Open"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::ifstream f(Name.c_str(), std::ios::binary);
|
std::ifstream f(Name.c_str(), std::ios::binary);
|
||||||
@ -38,72 +41,81 @@ void SendFile(Client*c,const std::string&Name){
|
|||||||
size_t Sent = 0;
|
size_t Sent = 0;
|
||||||
size_t Diff;
|
size_t Diff;
|
||||||
ssize_t Split = 64000;
|
ssize_t Split = 64000;
|
||||||
while(c->GetStatus() > -1 && Sent < Size){
|
while (c->GetStatus() > -1 && Sent < Size) {
|
||||||
Diff = Size - Sent;
|
Diff = Size - Sent;
|
||||||
if(Diff > size_t(Split)){
|
if (Diff > size_t(Split)) {
|
||||||
std::string Data(size_t(Split),0);
|
std::string Data(size_t(Split), 0);
|
||||||
f.seekg(ssize_t(Sent), std::ios_base::beg);
|
f.seekg(ssize_t(Sent), std::ios_base::beg);
|
||||||
f.read(&Data[0], Split);
|
f.read(&Data[0], Split);
|
||||||
STCPSend(c,Data);
|
STCPSend(c, Data);
|
||||||
Sent += size_t(Split);
|
Sent += size_t(Split);
|
||||||
}else{
|
} else {
|
||||||
std::string Data(Diff,0);
|
std::string Data(Diff, 0);
|
||||||
f.seekg(ssize_t(Sent), std::ios_base::beg);
|
f.seekg(ssize_t(Sent), std::ios_base::beg);
|
||||||
f.read(&Data[0], ssize_t(Diff));
|
f.read(&Data[0], ssize_t(Diff));
|
||||||
STCPSend(c,Data);
|
STCPSend(c, Data);
|
||||||
Sent += Diff;
|
Sent += Diff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parse(Client*c,const std::string&Packet){
|
void Parse(Client* c, const std::string& Packet) {
|
||||||
if(c == nullptr || Packet.empty())return;
|
if (c == nullptr || Packet.empty())
|
||||||
char Code = Packet.at(0),SubCode = 0;
|
return;
|
||||||
if(Packet.length() > 1)SubCode = Packet.at(1);
|
char Code = Packet.at(0), SubCode = 0;
|
||||||
|
if (Packet.length() > 1)
|
||||||
|
SubCode = Packet.at(1);
|
||||||
switch (Code) {
|
switch (Code) {
|
||||||
case 'f':
|
case 'f':
|
||||||
SendFile(c,Packet.substr(1));
|
SendFile(c, Packet.substr(1));
|
||||||
return;
|
return;
|
||||||
case 'S':
|
case 'S':
|
||||||
if(SubCode == 'R'){
|
if (SubCode == 'R') {
|
||||||
debug(Sec("Sending Mod Info"));
|
debug(Sec("Sending Mod Info"));
|
||||||
std::string ToSend = FileList+FileSizes;
|
std::string ToSend = FileList + FileSizes;
|
||||||
if(ToSend.empty())ToSend = "-";
|
if (ToSend.empty())
|
||||||
STCPSend(c,ToSend);
|
ToSend = "-";
|
||||||
}
|
STCPSend(c, ToSend);
|
||||||
return;
|
}
|
||||||
default:
|
return;
|
||||||
return;
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool STCPRecv(Client*c){
|
bool STCPRecv(Client* c) {
|
||||||
if(c == nullptr)return false;
|
if (c == nullptr)
|
||||||
|
return false;
|
||||||
char buf[200];
|
char buf[200];
|
||||||
size_t len = 200;
|
size_t len = 200;
|
||||||
ZeroMemory(buf, len);
|
ZeroMemory(buf, len);
|
||||||
ssize_t BytesRcv = recv(c->GetTCPSock(), buf, len,0);
|
ssize_t BytesRcv = recv(c->GetTCPSock(), buf, len, 0);
|
||||||
if (BytesRcv == 0){
|
if (BytesRcv == 0) {
|
||||||
if(c->GetStatus() > -1)c->SetStatus(-1);
|
if (c->GetStatus() > -1)
|
||||||
|
c->SetStatus(-1);
|
||||||
closesocket(c->GetTCPSock());
|
closesocket(c->GetTCPSock());
|
||||||
return false;
|
return false;
|
||||||
}else if (BytesRcv < 0) {
|
} else if (BytesRcv < 0) {
|
||||||
if(c->GetStatus() > -1)c->SetStatus(-1);
|
if (c->GetStatus() > -1)
|
||||||
|
c->SetStatus(-1);
|
||||||
closesocket(c->GetTCPSock());
|
closesocket(c->GetTCPSock());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(strcmp(buf,"Done") == 0)return false;
|
if (strcmp(buf, "Done") == 0)
|
||||||
|
return false;
|
||||||
std::string Ret(buf, size_t(BytesRcv));
|
std::string Ret(buf, size_t(BytesRcv));
|
||||||
Parse(c,Ret);
|
Parse(c, Ret);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncResources(Client*c){
|
void SyncResources(Client* c) {
|
||||||
if(c == nullptr)return;
|
if (c == nullptr)
|
||||||
try{
|
return;
|
||||||
STCPSend(c,Sec("WS"));
|
try {
|
||||||
while(c->GetStatus() > -1 && STCPRecv(c));
|
STCPSend(c, Sec("WS"));
|
||||||
}catch (std::exception& e){
|
while (c->GetStatus() > -1 && STCPRecv(c))
|
||||||
|
;
|
||||||
|
} catch (std::exception& e) {
|
||||||
except(Sec("Exception! : ") + std::string(e.what()));
|
except(Sec("Exception! : ") + std::string(e.what()));
|
||||||
c->SetStatus(-1);
|
c->SetStatus(-1);
|
||||||
}
|
}
|
||||||
|
@ -2,147 +2,155 @@
|
|||||||
/// Created by Anonymous275 on 5/8/2020
|
/// Created by Anonymous275 on 5/8/2020
|
||||||
///
|
///
|
||||||
///UDP
|
///UDP
|
||||||
#include "Security/Enc.h"
|
|
||||||
#include "Compressor.h"
|
|
||||||
#include "Client.hpp"
|
#include "Client.hpp"
|
||||||
#include "Settings.h"
|
#include "Compressor.h"
|
||||||
#include "Network.h"
|
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
|
#include "Network.h"
|
||||||
|
#include "Security/Enc.h"
|
||||||
|
#include "Settings.h"
|
||||||
#include "UnixCompat.h"
|
#include "UnixCompat.h"
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
#include <thread>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
int FC(const std::string& s,const std::string& p,int n);
|
#include <sstream>
|
||||||
struct PacketData{
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
int FC(const std::string& s, const std::string& p, int n);
|
||||||
|
struct PacketData {
|
||||||
int ID;
|
int ID;
|
||||||
::Client* Client;
|
::Client* Client;
|
||||||
std::string Data;
|
std::string Data;
|
||||||
int Tries;
|
int Tries;
|
||||||
};
|
};
|
||||||
struct SplitData{
|
struct SplitData {
|
||||||
int Total{};
|
int Total {};
|
||||||
int ID{};
|
int ID {};
|
||||||
std::set<std::pair<int,std::string>> Fragments;
|
std::set<std::pair<int, std::string>> Fragments;
|
||||||
};
|
};
|
||||||
|
|
||||||
SOCKET UDPSock;
|
SOCKET UDPSock;
|
||||||
std::set<PacketData*> DataAcks;
|
std::set<PacketData*> DataAcks;
|
||||||
std::set<SplitData*> SplitPackets;
|
std::set<SplitData*> SplitPackets;
|
||||||
void UDPSend(Client*c,std::string Data){
|
void UDPSend(Client* c, std::string Data) {
|
||||||
if(c == nullptr || !c->isConnected || c->GetStatus() < 0)return;
|
if (c == nullptr || !c->isConnected || c->GetStatus() < 0)
|
||||||
|
return;
|
||||||
sockaddr_in Addr = c->GetUDPAddr();
|
sockaddr_in Addr = c->GetUDPAddr();
|
||||||
socklen_t AddrSize = sizeof(c->GetUDPAddr());
|
socklen_t AddrSize = sizeof(c->GetUDPAddr());
|
||||||
Data = Data.substr(0,Data.find(char(0)));
|
Data = Data.substr(0, Data.find(char(0)));
|
||||||
if(Data.length() > 400){
|
if (Data.length() > 400) {
|
||||||
std::string CMP(Comp(Data));
|
std::string CMP(Comp(Data));
|
||||||
Data = "ABG:" + CMP;
|
Data = "ABG:" + CMP;
|
||||||
}
|
}
|
||||||
ssize_t sendOk = sendto(UDPSock, Data.c_str(), Data.size(), 0, (sockaddr *) &Addr, AddrSize);
|
ssize_t sendOk = sendto(UDPSock, Data.c_str(), Data.size(), 0, (sockaddr*)&Addr, AddrSize);
|
||||||
#ifdef __WIN32
|
#ifdef __WIN32
|
||||||
if (sendOk != 0) {
|
if (sendOk != 0) {
|
||||||
debug(Sec("(UDP) Send Failed Code : ") + std::to_string(WSAGetLastError()));
|
debug(Sec("(UDP) Send Failed Code : ") + std::to_string(WSAGetLastError()));
|
||||||
if(c->GetStatus() > -1)c->SetStatus(-1);
|
if (c->GetStatus() > -1)
|
||||||
|
c->SetStatus(-1);
|
||||||
}
|
}
|
||||||
#else // unix
|
#else // unix
|
||||||
if (sendOk != 0) {
|
if (sendOk != 0) {
|
||||||
debug(Sec("(UDP) Send Failed Code : ") + std::string(strerror(errno)));
|
debug(Sec("(UDP) Send Failed Code : ") + std::string(strerror(errno)));
|
||||||
if(c->GetStatus() > -1)c->SetStatus(-1);
|
if (c->GetStatus() > -1)
|
||||||
|
c->SetStatus(-1);
|
||||||
}
|
}
|
||||||
#endif // __WIN32
|
#endif // __WIN32
|
||||||
}
|
}
|
||||||
|
|
||||||
void AckID(int ID){
|
void AckID(int ID) {
|
||||||
for(PacketData* p : DataAcks){
|
for (PacketData* p : DataAcks) {
|
||||||
if(p != nullptr && p->ID == ID){
|
if (p != nullptr && p->ID == ID) {
|
||||||
DataAcks.erase(p);
|
DataAcks.erase(p);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int PacktID(){
|
int PacktID() {
|
||||||
static int ID = -1;
|
static int ID = -1;
|
||||||
if(ID > 999999)ID = 0;
|
if (ID > 999999)
|
||||||
else ID++;
|
ID = 0;
|
||||||
|
else
|
||||||
|
ID++;
|
||||||
return ID;
|
return ID;
|
||||||
}
|
}
|
||||||
int SplitID(){
|
int SplitID() {
|
||||||
static int SID = -1;
|
static int SID = -1;
|
||||||
if(SID > 999999)SID = 0;
|
if (SID > 999999)
|
||||||
else SID++;
|
SID = 0;
|
||||||
|
else
|
||||||
|
SID++;
|
||||||
return SID;
|
return SID;
|
||||||
}
|
}
|
||||||
void SendLarge(Client*c,std::string Data){
|
void SendLarge(Client* c, std::string Data) {
|
||||||
Data = Data.substr(0,Data.find(char(0)));
|
Data = Data.substr(0, Data.find(char(0)));
|
||||||
int ID = PacktID();
|
int ID = PacktID();
|
||||||
std::string Packet;
|
std::string Packet;
|
||||||
if(Data.length() > 1000){
|
if (Data.length() > 1000) {
|
||||||
std::string pckt = Data;
|
std::string pckt = Data;
|
||||||
int S = 1,Split = int(ceil(float(pckt.length()) / 1000));
|
int S = 1, Split = int(ceil(float(pckt.length()) / 1000));
|
||||||
int SID = SplitID();
|
int SID = SplitID();
|
||||||
while(pckt.length() > 1000){
|
while (pckt.length() > 1000) {
|
||||||
Packet = "SC|"+std::to_string(S)+"|"+std::to_string(Split)+"|"+std::to_string(ID)+"|"+
|
Packet = "SC|" + std::to_string(S) + "|" + std::to_string(Split) + "|" + std::to_string(ID) + "|" + std::to_string(SID) + "|" + pckt.substr(0, 1000);
|
||||||
std::to_string(SID)+"|"+pckt.substr(0,1000);
|
DataAcks.insert(new PacketData { ID, c, Packet, 1 });
|
||||||
DataAcks.insert(new PacketData{ID,c,Packet,1});
|
UDPSend(c, Packet);
|
||||||
UDPSend(c,Packet);
|
|
||||||
pckt = pckt.substr(1000);
|
pckt = pckt.substr(1000);
|
||||||
S++;
|
S++;
|
||||||
ID = PacktID();
|
ID = PacktID();
|
||||||
}
|
}
|
||||||
Packet = "SC|"+std::to_string(S)+"|"+std::to_string(Split)+"|"+
|
Packet = "SC|" + std::to_string(S) + "|" + std::to_string(Split) + "|" + std::to_string(ID) + "|" + std::to_string(SID) + "|" + pckt;
|
||||||
std::to_string(ID)+"|"+std::to_string(SID)+"|"+pckt;
|
DataAcks.insert(new PacketData { ID, c, Packet, 1 });
|
||||||
DataAcks.insert(new PacketData{ID,c,Packet,1});
|
UDPSend(c, Packet);
|
||||||
UDPSend(c,Packet);
|
} else {
|
||||||
}else{
|
|
||||||
Packet = "BD:" + std::to_string(ID) + ":" + Data;
|
Packet = "BD:" + std::to_string(ID) + ":" + Data;
|
||||||
DataAcks.insert(new PacketData{ID,c,Packet,1});
|
DataAcks.insert(new PacketData { ID, c, Packet, 1 });
|
||||||
UDPSend(c,Packet);
|
UDPSend(c, Packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
struct HandledC{
|
struct HandledC {
|
||||||
size_t Pos = 0;
|
size_t Pos = 0;
|
||||||
Client *c = nullptr;
|
Client* c = nullptr;
|
||||||
std::array<int, 100> HandledIDs = {-1};
|
std::array<int, 100> HandledIDs = { -1 };
|
||||||
};
|
};
|
||||||
std::set<HandledC*> HandledIDs;
|
std::set<HandledC*> HandledIDs;
|
||||||
void ResetIDs(HandledC*H){
|
void ResetIDs(HandledC* H) {
|
||||||
for(size_t C = 0;C < 100;C++){
|
for (size_t C = 0; C < 100; C++) {
|
||||||
H->HandledIDs.at(C) = -1;
|
H->HandledIDs.at(C) = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HandledC*GetHandled(Client*c){
|
HandledC* GetHandled(Client* c) {
|
||||||
for(HandledC*h : HandledIDs){
|
for (HandledC* h : HandledIDs) {
|
||||||
if(h->c == c){
|
if (h->c == c) {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new HandledC();
|
return new HandledC();
|
||||||
}
|
}
|
||||||
bool Handled(Client*c,int ID){
|
bool Handled(Client* c, int ID) {
|
||||||
bool handle = false;
|
bool handle = false;
|
||||||
for(HandledC*h : HandledIDs){
|
for (HandledC* h : HandledIDs) {
|
||||||
if(h->c == c){
|
if (h->c == c) {
|
||||||
for(int id : h->HandledIDs){
|
for (int id : h->HandledIDs) {
|
||||||
if(id == ID)return true;
|
if (id == ID)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if(h->Pos > 99)h->Pos = 0;
|
if (h->Pos > 99)
|
||||||
|
h->Pos = 0;
|
||||||
h->HandledIDs.at(h->Pos) = ID;
|
h->HandledIDs.at(h->Pos) = ID;
|
||||||
h->Pos++;
|
h->Pos++;
|
||||||
handle = true;
|
handle = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(HandledC*h : HandledIDs){
|
for (HandledC* h : HandledIDs) {
|
||||||
if(h->c == nullptr || !h->c->isConnected){
|
if (h->c == nullptr || !h->c->isConnected) {
|
||||||
HandledIDs.erase(h);
|
HandledIDs.erase(h);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!handle){
|
if (!handle) {
|
||||||
HandledC *h = GetHandled(c);
|
HandledC* h = GetHandled(c);
|
||||||
ResetIDs(h);
|
ResetIDs(h);
|
||||||
if (h->Pos > 99)h->Pos = 0;
|
if (h->Pos > 99)
|
||||||
|
h->Pos = 0;
|
||||||
h->HandledIDs.at(h->Pos) = ID;
|
h->HandledIDs.at(h->Pos) = ID;
|
||||||
h->Pos++;
|
h->Pos++;
|
||||||
h->c = c;
|
h->c = c;
|
||||||
@ -150,12 +158,12 @@ bool Handled(Client*c,int ID){
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::string UDPRcvFromClient(sockaddr_in& client){
|
std::string UDPRcvFromClient(sockaddr_in& client) {
|
||||||
size_t clientLength = sizeof(client);
|
size_t clientLength = sizeof(client);
|
||||||
ZeroMemory(&client, clientLength);
|
ZeroMemory(&client, clientLength);
|
||||||
std::string Ret(10240,0);
|
std::string Ret(10240, 0);
|
||||||
ssize_t Rcv = recvfrom(UDPSock, &Ret[0], 10240, 0, (sockaddr*)&client, (socklen_t*)&clientLength);
|
ssize_t Rcv = recvfrom(UDPSock, &Ret[0], 10240, 0, (sockaddr*)&client, (socklen_t*)&clientLength);
|
||||||
if (Rcv == -1){
|
if (Rcv == -1) {
|
||||||
#ifdef __WIN32
|
#ifdef __WIN32
|
||||||
error(Sec("(UDP) Error receiving from Client! Code : ") + std::to_string(WSAGetLastError()));
|
error(Sec("(UDP) Error receiving from Client! Code : ") + std::to_string(WSAGetLastError()));
|
||||||
#else // unix
|
#else // unix
|
||||||
@ -166,77 +174,80 @@ std::string UDPRcvFromClient(sockaddr_in& client){
|
|||||||
return Ret;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
SplitData*GetSplit(int SplitID){
|
SplitData* GetSplit(int SplitID) {
|
||||||
for(SplitData* a : SplitPackets){
|
for (SplitData* a : SplitPackets) {
|
||||||
if(a->ID == SplitID)return a;
|
if (a->ID == SplitID)
|
||||||
|
return a;
|
||||||
}
|
}
|
||||||
auto* SP = new SplitData();
|
auto* SP = new SplitData();
|
||||||
SplitPackets.insert(SP);
|
SplitPackets.insert(SP);
|
||||||
return SP;
|
return SP;
|
||||||
}
|
}
|
||||||
void HandleChunk(Client*c,const std::string&Data){
|
void HandleChunk(Client* c, const std::string& Data) {
|
||||||
int pos = FC(Data,"|",5);
|
int pos = FC(Data, "|", 5);
|
||||||
if(pos == -1)return;
|
if (pos == -1)
|
||||||
std::stringstream ss(Data.substr(0,size_t(pos++)));
|
return;
|
||||||
|
std::stringstream ss(Data.substr(0, size_t(pos++)));
|
||||||
std::string t;
|
std::string t;
|
||||||
int I = -1;
|
int I = -1;
|
||||||
//Current Max ID SID
|
//Current Max ID SID
|
||||||
std::vector<int> Num(4,0);
|
std::vector<int> Num(4, 0);
|
||||||
while (std::getline(ss, t, '|')) {
|
while (std::getline(ss, t, '|')) {
|
||||||
if(I >= 0)Num.at(size_t(I)) = std::stoi(t);
|
if (I >= 0)
|
||||||
|
Num.at(size_t(I)) = std::stoi(t);
|
||||||
I++;
|
I++;
|
||||||
}
|
}
|
||||||
std::string ack = "TRG:" + std::to_string(Num.at(2));
|
std::string ack = "TRG:" + std::to_string(Num.at(2));
|
||||||
UDPSend(c,ack);
|
UDPSend(c, ack);
|
||||||
if(Handled(c,Num.at(2))){
|
if (Handled(c, Num.at(2))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string Packet = Data.substr(size_t(pos));
|
std::string Packet = Data.substr(size_t(pos));
|
||||||
SplitData* SData = GetSplit(Num.at(3));
|
SplitData* SData = GetSplit(Num.at(3));
|
||||||
SData->Total = Num.at(1);
|
SData->Total = Num.at(1);
|
||||||
SData->ID = Num.at(3);
|
SData->ID = Num.at(3);
|
||||||
SData->Fragments.insert(std::make_pair(Num.at(0),Packet));
|
SData->Fragments.insert(std::make_pair(Num.at(0), Packet));
|
||||||
if(SData->Fragments.size() == size_t(SData->Total)) {
|
if (SData->Fragments.size() == size_t(SData->Total)) {
|
||||||
std::string ToHandle;
|
std::string ToHandle;
|
||||||
for(const std::pair<int,std::string>& a : SData->Fragments){
|
for (const std::pair<int, std::string>& a : SData->Fragments) {
|
||||||
ToHandle += a.second;
|
ToHandle += a.second;
|
||||||
}
|
}
|
||||||
GParser(c,ToHandle);
|
GParser(c, ToHandle);
|
||||||
SplitPackets.erase(SData);
|
SplitPackets.erase(SData);
|
||||||
delete SData;
|
delete SData;
|
||||||
SData = nullptr;
|
SData = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void UDPParser(Client*c,std::string Packet){
|
void UDPParser(Client* c, std::string Packet) {
|
||||||
if(Packet.substr(0,4) == "ABG:"){
|
if (Packet.substr(0, 4) == "ABG:") {
|
||||||
Packet = DeComp(Packet.substr(4));
|
Packet = DeComp(Packet.substr(4));
|
||||||
}
|
}
|
||||||
if(Packet.substr(0,4) == "TRG:"){
|
if (Packet.substr(0, 4) == "TRG:") {
|
||||||
std::string pkt = Packet.substr(4);
|
std::string pkt = Packet.substr(4);
|
||||||
if(Packet.find_first_not_of("0123456789") == std::string::npos){
|
if (Packet.find_first_not_of("0123456789") == std::string::npos) {
|
||||||
AckID(stoi(Packet));
|
AckID(stoi(Packet));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}else if(Packet.substr(0,3) == "BD:"){
|
} else if (Packet.substr(0, 3) == "BD:") {
|
||||||
auto pos = Packet.find(':',4);
|
auto pos = Packet.find(':', 4);
|
||||||
int ID = stoi(Packet.substr(3,pos-3));
|
int ID = stoi(Packet.substr(3, pos - 3));
|
||||||
std::string pkt = "TRG:" + std::to_string(ID);
|
std::string pkt = "TRG:" + std::to_string(ID);
|
||||||
UDPSend(c,pkt);
|
UDPSend(c, pkt);
|
||||||
if(!Handled(c,ID)) {
|
if (!Handled(c, ID)) {
|
||||||
pkt = Packet.substr(pos + 1);
|
pkt = Packet.substr(pos + 1);
|
||||||
GParser(c, pkt);
|
GParser(c, pkt);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}else if(Packet.substr(0,2) == "SC"){
|
} else if (Packet.substr(0, 2) == "SC") {
|
||||||
HandleChunk(c,Packet);
|
HandleChunk(c, Packet);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GParser(c,Packet);
|
GParser(c, Packet);
|
||||||
}
|
}
|
||||||
void LOOP(){
|
void LOOP() {
|
||||||
while(UDPSock != -1) {
|
while (UDPSock != -1) {
|
||||||
for (PacketData* p : DataAcks){
|
for (PacketData* p : DataAcks) {
|
||||||
if(p != nullptr) {
|
if (p != nullptr) {
|
||||||
if (p->Client == nullptr || p->Client->GetTCPSock() == -1) {
|
if (p->Client == nullptr || p->Client->GetTCPSock() == -1) {
|
||||||
DataAcks.erase(p);
|
DataAcks.erase(p);
|
||||||
break;
|
break;
|
||||||
@ -253,23 +264,23 @@ void LOOP(){
|
|||||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[[noreturn]] void UDPServerMain(){
|
[[noreturn]] void UDPServerMain() {
|
||||||
#ifdef __WIN32
|
#ifdef __WIN32
|
||||||
WSADATA data;
|
WSADATA data;
|
||||||
if (WSAStartup(514, &data)){
|
if (WSAStartup(514, &data)) {
|
||||||
error(Sec("Can't start Winsock!"));
|
error(Sec("Can't start Winsock!"));
|
||||||
//return;
|
//return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
// Create a server hint structure for the server
|
// Create a server hint structure for the server
|
||||||
sockaddr_in serverAddr{};
|
sockaddr_in serverAddr {};
|
||||||
serverAddr.sin_addr.S_un.S_addr = ADDR_ANY; //Any Local
|
serverAddr.sin_addr.S_un.S_addr = ADDR_ANY; //Any Local
|
||||||
serverAddr.sin_family = AF_INET; // Address format is IPv4
|
serverAddr.sin_family = AF_INET; // Address format is IPv4
|
||||||
serverAddr.sin_port = htons(Port); // Convert from little to big endian
|
serverAddr.sin_port = htons(Port); // Convert from little to big endian
|
||||||
|
|
||||||
// Try and bind the socket to the IP and port
|
// Try and bind the socket to the IP and port
|
||||||
if (bind(UDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR){
|
if (bind(UDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
|
||||||
error(Sec("Can't bind socket!") + std::to_string(WSAGetLastError()));
|
error(Sec("Can't bind socket!") + std::to_string(WSAGetLastError()));
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
exit(-1);
|
exit(-1);
|
||||||
@ -280,21 +291,22 @@ void LOOP(){
|
|||||||
std::thread Ack(LOOP);
|
std::thread Ack(LOOP);
|
||||||
Ack.detach();
|
Ack.detach();
|
||||||
|
|
||||||
info(Sec("Vehicle data network online on port ")+std::to_string(Port)+Sec(" with a Max of ")+std::to_string(MaxPlayers)+Sec(" Clients"));
|
info(Sec("Vehicle data network online on port ") + std::to_string(Port) + Sec(" with a Max of ") + std::to_string(MaxPlayers) + Sec(" Clients"));
|
||||||
while (true){
|
while (true) {
|
||||||
sockaddr_in client{};
|
sockaddr_in client {};
|
||||||
std::string Data = UDPRcvFromClient(client); //Receives any data from Socket
|
std::string Data = UDPRcvFromClient(client); //Receives any data from Socket
|
||||||
auto Pos = Data.find(':');
|
auto Pos = Data.find(':');
|
||||||
if(Data.empty() || Pos < 0 || Pos > 2)continue;
|
if (Data.empty() || Pos < 0 || Pos > 2)
|
||||||
|
continue;
|
||||||
/*char clientIp[256];
|
/*char clientIp[256];
|
||||||
ZeroMemory(clientIp, 256); ///Code to get IP we don't need that yet
|
ZeroMemory(clientIp, 256); ///Code to get IP we don't need that yet
|
||||||
inet_ntop(AF_INET, &client.sin_addr, clientIp, 256);*/
|
inet_ntop(AF_INET, &client.sin_addr, clientIp, 256);*/
|
||||||
uint8_t ID = Data.at(0)-1;
|
uint8_t ID = Data.at(0) - 1;
|
||||||
for(Client*c : CI->Clients){
|
for (Client* c : CI->Clients) {
|
||||||
if(c != nullptr && c->GetID() == ID){
|
if (c != nullptr && c->GetID() == ID) {
|
||||||
c->SetUDPAddr(client);
|
c->SetUDPAddr(client);
|
||||||
c->isConnected = true;
|
c->isConnected = true;
|
||||||
UDPParser(c,Data.substr(2));
|
UDPParser(c, Data.substr(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,13 +316,13 @@ void LOOP(){
|
|||||||
#else // unix
|
#else // unix
|
||||||
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
// Create a server hint structure for the server
|
// Create a server hint structure for the server
|
||||||
sockaddr_in serverAddr{};
|
sockaddr_in serverAddr {};
|
||||||
serverAddr.sin_addr.s_addr = INADDR_ANY; //Any Local
|
serverAddr.sin_addr.s_addr = INADDR_ANY; //Any Local
|
||||||
serverAddr.sin_family = AF_INET; // Address format is IPv4
|
serverAddr.sin_family = AF_INET; // Address format is IPv4
|
||||||
serverAddr.sin_port = htons(uint16_t(Port)); // Convert from little to big endian
|
serverAddr.sin_port = htons(uint16_t(Port)); // Convert from little to big endian
|
||||||
|
|
||||||
// Try and bind the socket to the IP and port
|
// Try and bind the socket to the IP and port
|
||||||
if (bind(UDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0){
|
if (bind(UDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) {
|
||||||
error(Sec("Can't bind socket!") + std::string(strerror(errno)));
|
error(Sec("Can't bind socket!") + std::string(strerror(errno)));
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
exit(-1);
|
exit(-1);
|
||||||
@ -321,21 +333,22 @@ void LOOP(){
|
|||||||
std::thread Ack(LOOP);
|
std::thread Ack(LOOP);
|
||||||
Ack.detach();
|
Ack.detach();
|
||||||
|
|
||||||
info(Sec("Vehicle data network online on port ")+std::to_string(Port)+Sec(" with a Max of ")+std::to_string(MaxPlayers)+Sec(" Clients"));
|
info(Sec("Vehicle data network online on port ") + std::to_string(Port) + Sec(" with a Max of ") + std::to_string(MaxPlayers) + Sec(" Clients"));
|
||||||
while (true){
|
while (true) {
|
||||||
sockaddr_in client{};
|
sockaddr_in client {};
|
||||||
std::string Data = UDPRcvFromClient(client); //Receives any data from Socket
|
std::string Data = UDPRcvFromClient(client); //Receives any data from Socket
|
||||||
size_t Pos = Data.find(':');
|
size_t Pos = Data.find(':');
|
||||||
if(Data.empty() || Pos > 2)continue;
|
if (Data.empty() || Pos > 2)
|
||||||
|
continue;
|
||||||
/*char clientIp[256];
|
/*char clientIp[256];
|
||||||
ZeroMemory(clientIp, 256); ///Code to get IP we don't need that yet
|
ZeroMemory(clientIp, 256); ///Code to get IP we don't need that yet
|
||||||
inet_ntop(AF_INET, &client.sin_addr, clientIp, 256);*/
|
inet_ntop(AF_INET, &client.sin_addr, clientIp, 256);*/
|
||||||
uint8_t ID = uint8_t(Data.at(0)) - 1;
|
uint8_t ID = uint8_t(Data.at(0)) - 1;
|
||||||
for(Client*c : CI->Clients){
|
for (Client* c : CI->Clients) {
|
||||||
if(c != nullptr && c->GetID() == ID){
|
if (c != nullptr && c->GetID() == ID) {
|
||||||
c->SetUDPAddr(client);
|
c->SetUDPAddr(client);
|
||||||
c->isConnected = true;
|
c->isConnected = true;
|
||||||
UDPParser(c,Data.substr(2));
|
UDPParser(c, Data.substr(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user