Add A2S_Rules fix for CS:GO to CStrike extension. (#614)
* Add A2S_Rules fix for CS:GO to CStrike extension. * Dont force set the cvar. Add checks before patching. * Remove incorrect and useless check. * Remove checking value as it is in the signature. * Update build script changes to AMBuild 2.1 API to fix build. * Fix bad check and ConVarRef being resolved too early. * Whoops. This line is kinda important. Got nuked when refactoring. * Remove unused variable. * Updatet gamedata. * Switch to "Addresses" gamedata lookup * Add linux64 gamedata * Fix mac build Co-authored-by: Ruben Gonzalez <drifter01620@gmail.com> Co-authored-by: Peace-Maker <peace-maker@wcfan.de>
This commit is contained in:
parent
6c2be9bdcc
commit
ae485c3115
@ -24,11 +24,46 @@ for sdk_name in ['css', 'csgo']:
|
|||||||
if sdk_name not in SM.sdks:
|
if sdk_name not in SM.sdks:
|
||||||
continue
|
continue
|
||||||
sdk = SM.sdks[sdk_name]
|
sdk = SM.sdks[sdk_name]
|
||||||
|
|
||||||
|
if sdk_name == 'csgo':
|
||||||
|
project.sources += ['rulesfix.cpp']
|
||||||
|
|
||||||
for cxx in builder.targets:
|
for cxx in builder.targets:
|
||||||
if not cxx.target.arch in sdk.platformSpec[cxx.target.platform]:
|
if not cxx.target.arch in sdk.platformSpec[cxx.target.platform]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cxx.defines += ['HAVE_STRING_H'];
|
cxx.defines += ['HAVE_STRING_H']
|
||||||
SM.HL2ExtConfig(project, builder, cxx, 'game.cstrike.ext.' + sdk.ext, sdk)
|
binary = SM.HL2ExtConfig(project, builder, cxx, 'game.cstrike.ext.' + sdk.ext, sdk)
|
||||||
|
if sdk_name == 'csgo':
|
||||||
|
compiler = binary.compiler
|
||||||
|
compiler.cxxincludes += [os.path.join(sdk.path, 'public', 'steam')]
|
||||||
|
compiler.defines += ['VERSION_SAFE_STEAM_API_INTERFACES']
|
||||||
|
|
||||||
|
library_name = ''
|
||||||
|
if cxx.target.platform == 'windows':
|
||||||
|
compiler.linkflags += [os.path.join(sdk.path, 'lib', 'public', 'steam_api.lib')]
|
||||||
|
elif cxx.target.platform == 'linux':
|
||||||
|
library_name = 'libsteam_api.so'
|
||||||
|
if cxx.target.arch == 'x86_64':
|
||||||
|
source_path = os.path.join(sdk.path, 'lib', 'linux64')
|
||||||
|
else:
|
||||||
|
source_path = os.path.join(sdk.path, 'lib', 'linux')
|
||||||
|
elif cxx.target.platform == 'mac':
|
||||||
|
library_name = 'libsteam_api.dylib'
|
||||||
|
if cxx.target.arch == 'x86_64':
|
||||||
|
source_path = os.path.join(sdk.path, 'lib', 'osx64')
|
||||||
|
else:
|
||||||
|
source_path = os.path.join(sdk.path, 'lib', 'mac')
|
||||||
|
|
||||||
|
if library_name:
|
||||||
|
source_path = os.path.join(source_path, library_name)
|
||||||
|
output_path = os.path.join(binary.localFolder, library_name)
|
||||||
|
|
||||||
|
# Ensure the output path exists.
|
||||||
|
builder.AddFolder(binary.localFolder)
|
||||||
|
output = builder.AddSymlink(source_path, output_path)
|
||||||
|
|
||||||
|
compiler.weaklinkdeps += [output]
|
||||||
|
compiler.linkflags[0:0] = [library_name]
|
||||||
|
|
||||||
SM.extensions += builder.Add(project)
|
SM.extensions += builder.Add(project)
|
||||||
|
@ -36,6 +36,9 @@
|
|||||||
#include "iplayerinfo.h"
|
#include "iplayerinfo.h"
|
||||||
#include "ISDKTools.h"
|
#include "ISDKTools.h"
|
||||||
#include "forwards.h"
|
#include "forwards.h"
|
||||||
|
#if SOURCE_ENGINE == SE_CSGO
|
||||||
|
#include "rulesfix.h"
|
||||||
|
#endif
|
||||||
#include "util_cstrike.h"
|
#include "util_cstrike.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,6 +91,10 @@ bool CStrike::SDK_OnLoad(char *error, size_t maxlength, bool late)
|
|||||||
|
|
||||||
playerhelpers->RegisterCommandTargetProcessor(this);
|
playerhelpers->RegisterCommandTargetProcessor(this);
|
||||||
|
|
||||||
|
#if SOURCE_ENGINE == SE_CSGO
|
||||||
|
rulesfix.OnLoad();
|
||||||
|
#endif
|
||||||
|
|
||||||
CDetourManager::Init(g_pSM->GetScriptingEngine(), g_pGameConf);
|
CDetourManager::Init(g_pSM->GetScriptingEngine(), g_pGameConf);
|
||||||
|
|
||||||
g_pHandleBuyForward = forwards->CreateForward("CS_OnBuyCommand", ET_Event, 2, NULL, Param_Cell, Param_String);
|
g_pHandleBuyForward = forwards->CreateForward("CS_OnBuyCommand", ET_Event, 2, NULL, Param_Cell, Param_String);
|
||||||
@ -110,6 +117,12 @@ bool CStrike::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool
|
|||||||
GET_V_IFACE_CURRENT(GetEngineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER);
|
GET_V_IFACE_CURRENT(GetEngineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER);
|
||||||
gpGlobals = ismm->GetCGlobals();
|
gpGlobals = ismm->GetCGlobals();
|
||||||
|
|
||||||
|
#if SOURCE_ENGINE == SE_CSGO
|
||||||
|
ICvar *icvar;
|
||||||
|
GET_V_IFACE_CURRENT(GetEngineFactory, icvar, ICvar, CVAR_INTERFACE_VERSION);
|
||||||
|
g_pCVar = icvar;
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +166,7 @@ void CStrike::SDK_OnUnload()
|
|||||||
forwards->ReleaseForward(g_pCSWeaponDropForward);
|
forwards->ReleaseForward(g_pCSWeaponDropForward);
|
||||||
|
|
||||||
#if SOURCE_ENGINE == SE_CSGO
|
#if SOURCE_ENGINE == SE_CSGO
|
||||||
|
rulesfix.OnUnload();
|
||||||
ClearHashMaps();
|
ClearHashMaps();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
232
extensions/cstrike/rulesfix.cpp
Normal file
232
extensions/cstrike/rulesfix.cpp
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
/**
|
||||||
|
* vim: set ts=4 :
|
||||||
|
* =============================================================================
|
||||||
|
* SourceMod Counter-Strike:Source Extension
|
||||||
|
* Copyright (C) 2017 AlliedModders LLC. All rights reserved.
|
||||||
|
* =============================================================================
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||||
|
* Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||||
|
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||||
|
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||||
|
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||||
|
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||||
|
* this exception to all derivative works. AlliedModders LLC defines further
|
||||||
|
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||||
|
* or <http://www.sourcemod.net/license.php>.
|
||||||
|
*
|
||||||
|
* Version: $Id$
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rulesfix.h"
|
||||||
|
#include "extension.h"
|
||||||
|
#include <sh_memory.h>
|
||||||
|
|
||||||
|
// Grab the convar ref
|
||||||
|
ConVar *host_rules_show = nullptr;
|
||||||
|
bool bPatched = false;
|
||||||
|
|
||||||
|
RulesFix rulesfix;
|
||||||
|
|
||||||
|
SH_DECL_HOOK1_void(IServerGameDLL, GameServerSteamAPIActivated, SH_NOATTRIB, 0, bool);
|
||||||
|
|
||||||
|
RulesFix::RulesFix() :
|
||||||
|
m_OnSteamServersConnected(this, &RulesFix::OnSteamServersConnected)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMTUMax(int iValue)
|
||||||
|
{
|
||||||
|
static int iOriginalValue = -1;
|
||||||
|
static int *m_pMaxMTU = nullptr;
|
||||||
|
|
||||||
|
//If we never changed skip resetting
|
||||||
|
if (iOriginalValue == -1 && iValue == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_pMaxMTU == nullptr)
|
||||||
|
{
|
||||||
|
if (!g_pGameConf->GetAddress("MaxMTU", (void **)&m_pMaxMTU))
|
||||||
|
{
|
||||||
|
g_pSM->LogMessage(myself, "[CStrike] Failed to locate NET_SendPacket signature.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceHook::SetMemAccess(m_pMaxMTU, sizeof(int), SH_MEM_READ | SH_MEM_WRITE | SH_MEM_EXEC);
|
||||||
|
|
||||||
|
iOriginalValue = *m_pMaxMTU;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iValue == -1)
|
||||||
|
*m_pMaxMTU = iOriginalValue;
|
||||||
|
else
|
||||||
|
*m_pMaxMTU = iValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RulesFix::OnLoad()
|
||||||
|
{
|
||||||
|
host_rules_show = g_pCVar->FindVar("host_rules_show");
|
||||||
|
if (host_rules_show)
|
||||||
|
{
|
||||||
|
// Default to enabled. Explicit disable via cfg will still be obeyed.
|
||||||
|
host_rules_show->SetValue(true);
|
||||||
|
|
||||||
|
SetMTUMax(5000);
|
||||||
|
bPatched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SH_ADD_HOOK(IServerGameDLL, GameServerSteamAPIActivated, gamedll, SH_MEMBER(this, &RulesFix::Hook_GameServerSteamAPIActivated), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RulesFix::OnUnload()
|
||||||
|
{
|
||||||
|
SetMTUMax(-1);
|
||||||
|
SH_REMOVE_HOOK(IServerGameDLL, GameServerSteamAPIActivated, gamedll, SH_MEMBER(this, &RulesFix::Hook_GameServerSteamAPIActivated), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotifyAllCVars()
|
||||||
|
{
|
||||||
|
ICvar::Iterator iter(g_pCVar);
|
||||||
|
for (iter.SetFirst(); iter.IsValid(); iter.Next())
|
||||||
|
{
|
||||||
|
ConCommandBase *cmd = iter.Get();
|
||||||
|
if (!cmd->IsCommand() && cmd->IsFlagSet(FCVAR_NOTIFY))
|
||||||
|
{
|
||||||
|
rulesfix.OnNotifyConVarChanged((ConVar *)cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RulesFix::OnSteamServersConnected(SteamServersConnected_t *)
|
||||||
|
{
|
||||||
|
// The engine clears all after the Steam interfaces become available after they've been gone.
|
||||||
|
NotifyAllCVars();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnConVarChanged(IConVar *var, const char *pOldValue, float flOldValue)
|
||||||
|
{
|
||||||
|
if (host_rules_show && var == host_rules_show)
|
||||||
|
{
|
||||||
|
if (host_rules_show->GetBool())
|
||||||
|
{
|
||||||
|
if (!bPatched)
|
||||||
|
{
|
||||||
|
SetMTUMax(5000);
|
||||||
|
bPatched = true;
|
||||||
|
NotifyAllCVars();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (bPatched)
|
||||||
|
{
|
||||||
|
SetMTUMax(-1);
|
||||||
|
bPatched = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (var->IsFlagSet(FCVAR_NOTIFY))
|
||||||
|
{
|
||||||
|
rulesfix.OnNotifyConVarChanged((ConVar *)var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RulesFix::OnNotifyConVarChanged(ConVar *pVar)
|
||||||
|
{
|
||||||
|
if (!bPatched)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_Steam.SteamMasterServerUpdater())
|
||||||
|
{
|
||||||
|
if (pVar->IsFlagSet(FCVAR_PROTECTED))
|
||||||
|
{
|
||||||
|
if (!pVar->GetString()[0])
|
||||||
|
{
|
||||||
|
m_Steam.SteamMasterServerUpdater()->SetKeyValue(pVar->GetName(), "0");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Steam.SteamMasterServerUpdater()->SetKeyValue(pVar->GetName(), "1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Steam.SteamMasterServerUpdater()->SetKeyValue(pVar->GetName(), pVar->GetString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RulesFix::Hook_GameServerSteamAPIActivated(bool bActivated)
|
||||||
|
{
|
||||||
|
if (bActivated)
|
||||||
|
{
|
||||||
|
FixSteam();
|
||||||
|
m_Steam.Init();
|
||||||
|
|
||||||
|
g_pCVar->InstallGlobalChangeCallback(OnConVarChanged);
|
||||||
|
OnSteamServersConnected(nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_pCVar->RemoveGlobalChangeCallback(OnConVarChanged);
|
||||||
|
m_Steam.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RulesFix::FixSteam()
|
||||||
|
{
|
||||||
|
if (!g_pSteamClientGameServer)
|
||||||
|
{
|
||||||
|
void *(*pGSInternalCreateAddress)(const char *) = nullptr;
|
||||||
|
void *(*pInternalCreateAddress)(const char *) = nullptr;
|
||||||
|
|
||||||
|
// CS:GO currently uses the old name, but will use the new name when they update to a
|
||||||
|
// newer Steamworks SDK. Stay compatible.
|
||||||
|
const char *pGSInternalFuncName = "SteamGameServerInternal_CreateInterface";
|
||||||
|
const char *pInternalFuncName = "SteamInternal_CreateInterface";
|
||||||
|
|
||||||
|
ILibrary *pLibrary = libsys->OpenLibrary(
|
||||||
|
#if defined ( PLATFORM_WINDOWS )
|
||||||
|
"steam_api.dll"
|
||||||
|
#elif defined( PLATFORM_LINUX )
|
||||||
|
"libsteam_api.so"
|
||||||
|
#elif defined( PLATFORM_APPLE )
|
||||||
|
"libsteam_api.dylib"
|
||||||
|
#else
|
||||||
|
#error Unsupported platform
|
||||||
|
#endif
|
||||||
|
, nullptr, 0);
|
||||||
|
if (pLibrary != nullptr)
|
||||||
|
{
|
||||||
|
if (pGSInternalCreateAddress == nullptr)
|
||||||
|
{
|
||||||
|
pGSInternalCreateAddress = reinterpret_cast<void *(*)(const char *)>(pLibrary->GetSymbolAddress(pGSInternalFuncName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pInternalCreateAddress == nullptr)
|
||||||
|
{
|
||||||
|
pInternalCreateAddress = reinterpret_cast<void *(*)(const char *)>(pLibrary->GetSymbolAddress(pInternalFuncName));
|
||||||
|
}
|
||||||
|
|
||||||
|
pLibrary->CloseLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pGSInternalCreateAddress != nullptr)
|
||||||
|
g_pSteamClientGameServer = reinterpret_cast<ISteamClient *>((*pGSInternalCreateAddress)(STEAMCLIENT_INTERFACE_VERSION));
|
||||||
|
|
||||||
|
if (g_pSteamClientGameServer == nullptr && pInternalCreateAddress != nullptr)
|
||||||
|
g_pSteamClientGameServer = reinterpret_cast<ISteamClient *>((*pInternalCreateAddress)(STEAMCLIENT_INTERFACE_VERSION));
|
||||||
|
}
|
||||||
|
}
|
52
extensions/cstrike/rulesfix.h
Normal file
52
extensions/cstrike/rulesfix.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* vim: set ts=4 :
|
||||||
|
* =============================================================================
|
||||||
|
* SourceMod Counter-Strike:Source Extension
|
||||||
|
* Copyright (C) 2017 AlliedModders LLC. All rights reserved.
|
||||||
|
* =============================================================================
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||||
|
* Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||||
|
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||||
|
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||||
|
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||||
|
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||||
|
* this exception to all derivative works. AlliedModders LLC defines further
|
||||||
|
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||||
|
* or <http://www.sourcemod.net/license.php>.
|
||||||
|
*
|
||||||
|
* Version: $Id$
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <steam_gameserver.h>
|
||||||
|
class ConVar;
|
||||||
|
|
||||||
|
class RulesFix
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RulesFix();
|
||||||
|
void OnLoad();
|
||||||
|
void OnUnload();
|
||||||
|
void OnNotifyConVarChanged(ConVar *pVar);
|
||||||
|
void Hook_GameServerSteamAPIActivated(bool bActivated);
|
||||||
|
private:
|
||||||
|
void FixSteam();
|
||||||
|
private:
|
||||||
|
CSteamGameServerAPIContext m_Steam;
|
||||||
|
STEAM_GAMESERVER_CALLBACK(RulesFix, OnSteamServersConnected, SteamServersConnected_t, m_OnSteamServersConnected);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern RulesFix rulesfix;
|
@ -68,7 +68,9 @@
|
|||||||
#define SMEXT_ENABLE_GAMEHELPERS
|
#define SMEXT_ENABLE_GAMEHELPERS
|
||||||
#define SMEXT_ENABLE_TIMERSYS
|
#define SMEXT_ENABLE_TIMERSYS
|
||||||
//#define SMEXT_ENABLE_THREADER
|
//#define SMEXT_ENABLE_THREADER
|
||||||
//#define SMEXT_ENABLE_LIBSYS
|
#if SOURCE_ENGINE == SE_CSGO
|
||||||
|
#define SMEXT_ENABLE_LIBSYS
|
||||||
|
#endif
|
||||||
#define SMEXT_ENABLE_USERMSGS
|
#define SMEXT_ENABLE_USERMSGS
|
||||||
#define SMEXT_ENABLE_PLUGINSYS
|
#define SMEXT_ENABLE_PLUGINSYS
|
||||||
|
|
||||||
|
@ -264,4 +264,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"#default"
|
||||||
|
{
|
||||||
|
"Addresses"
|
||||||
|
{
|
||||||
|
// Offset from NET_SendPacket sig to MTU
|
||||||
|
"MaxMTU"
|
||||||
|
{
|
||||||
|
"windows"
|
||||||
|
{
|
||||||
|
"signature" "NET_SendPacket"
|
||||||
|
"offset" "6"
|
||||||
|
}
|
||||||
|
"linux"
|
||||||
|
{
|
||||||
|
"signature" "NET_SendPacket"
|
||||||
|
"offset" "5"
|
||||||
|
}
|
||||||
|
"linux64"
|
||||||
|
{
|
||||||
|
"signature" "NET_SendPacket"
|
||||||
|
"offset" "4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"Signatures"
|
||||||
|
{
|
||||||
|
// This isn't the start of the function, but closer to where we want to patch, including the exact bytes we will replace
|
||||||
|
"NET_SendPacket"
|
||||||
|
{
|
||||||
|
"library" "engine"
|
||||||
|
// "[NET] Cannot send %d-byte packet to %s. MTU is %u. %02x %02x %02x %02x %02x\n"
|
||||||
|
"windows" "\x89\x4C\x24\x14\x81\xFF\xB0\x04\x00\x00\x7E"
|
||||||
|
// _Z14NET_SendPacketP11INetChanneliRK10ns_addressPKhiP8bf_writebj
|
||||||
|
"linux" "\x8B\x7D\x10\x81\xFE\xB0\x04\x00\x00\x0F"
|
||||||
|
"linux64" "\x31\xC0\x81\xF9\xB0\x04\x00\x00\x0F"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user