mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-07-22 11:53:16 +00:00
Applied patch to fix some race conditions on call termination, thanks Peter Olsson
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@13014 d0543943-73ff-0310-b7d9-9358b9ac24b2
This commit is contained in:
parent
d5bf92f159
commit
59b3cdc2a6
@ -72,12 +72,12 @@ static switch_state_handler_table_t opalfs_event_handlers = {
|
|||||||
/*.on_hangup */ on_hangup,
|
/*.on_hangup */ on_hangup,
|
||||||
/*.on_exchange_media */ FSConnection::on_exchange_media,
|
/*.on_exchange_media */ FSConnection::on_exchange_media,
|
||||||
/*.on_soft_execute */ FSConnection::on_soft_execute,
|
/*.on_soft_execute */ FSConnection::on_soft_execute,
|
||||||
/*.on_consume_media*/ NULL,
|
/*.on_consume_media*/ NULL,
|
||||||
/*.on_hibernate*/ NULL,
|
/*.on_hibernate*/ NULL,
|
||||||
/*.on_reset*/ NULL,
|
/*.on_reset*/ NULL,
|
||||||
/*.on_park*/ NULL,
|
/*.on_park*/ NULL,
|
||||||
/*.on_reporting*/ NULL,
|
/*.on_reporting*/ NULL,
|
||||||
/*.on_destroy*/ on_destroy
|
/*.on_destroy*/ on_destroy
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -510,7 +510,20 @@ bool FSEndPoint::OnIncomingCall(OpalLocalConnection & connection)
|
|||||||
|
|
||||||
OpalLocalConnection *FSEndPoint::CreateConnection(OpalCall & call, void *userData)
|
OpalLocalConnection *FSEndPoint::CreateConnection(OpalCall & call, void *userData)
|
||||||
{
|
{
|
||||||
return new FSConnection(call, *this, (switch_caller_profile_t *)userData);
|
FSManager & mgr = (FSManager &) GetManager();
|
||||||
|
switch_core_session_t *fsSession = switch_core_session_request(mgr.GetSwitchInterface(),
|
||||||
|
(switch_caller_profile_t *)userData ? SWITCH_CALL_DIRECTION_OUTBOUND : SWITCH_CALL_DIRECTION_INBOUND, NULL);
|
||||||
|
if (fsSession == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
switch_channel_t *fsChannel = switch_core_session_get_channel(fsSession);
|
||||||
|
|
||||||
|
if (fsChannel == NULL) {
|
||||||
|
switch_core_session_destroy(&fsSession);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FSConnection(call, *this, (switch_caller_profile_t *)userData, fsSession, fsChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -543,15 +556,13 @@ PBoolean FSCall::OnSetUp(OpalConnection & connection)
|
|||||||
///////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
FSConnection::FSConnection(OpalCall & call, FSEndPoint & endpoint, switch_caller_profile_t *outbound_profile)
|
FSConnection::FSConnection(OpalCall & call, FSEndPoint & endpoint, switch_caller_profile_t *outbound_profile, switch_core_session_t *fsSession, switch_channel_t *fsChannel)
|
||||||
: OpalLocalConnection(call, endpoint, NULL)
|
: OpalLocalConnection(call, endpoint, NULL)
|
||||||
, m_endpoint(endpoint)
|
, m_endpoint(endpoint)
|
||||||
|
, m_fsSession(fsSession)
|
||||||
|
, m_fsChannel(fsChannel)
|
||||||
{
|
{
|
||||||
opal_private_t *tech_pvt;
|
opal_private_t *tech_pvt;
|
||||||
FSManager & mgr = (FSManager &) endpoint.GetManager();
|
|
||||||
m_fsSession = switch_core_session_request(mgr.GetSwitchInterface(),
|
|
||||||
outbound_profile ? SWITCH_CALL_DIRECTION_OUTBOUND : SWITCH_CALL_DIRECTION_INBOUND, NULL);
|
|
||||||
m_fsChannel = switch_core_session_get_channel(m_fsSession);
|
|
||||||
|
|
||||||
tech_pvt = (opal_private_t *) switch_core_session_alloc(m_fsSession, sizeof(*tech_pvt));
|
tech_pvt = (opal_private_t *) switch_core_session_alloc(m_fsSession, sizeof(*tech_pvt));
|
||||||
tech_pvt->me = this;
|
tech_pvt->me = this;
|
||||||
@ -679,8 +690,7 @@ void FSConnection::OnEstablished()
|
|||||||
PBoolean FSConnection::SendUserInputTone(char tone, unsigned duration)
|
PBoolean FSConnection::SendUserInputTone(char tone, unsigned duration)
|
||||||
{
|
{
|
||||||
switch_dtmf_t dtmf = { tone, duration };
|
switch_dtmf_t dtmf = { tone, duration };
|
||||||
|
return switch_channel_queue_dtmf(m_fsChannel, &dtmf) == SWITCH_STATUS_SUCCESS;
|
||||||
return (switch_channel_queue_dtmf(m_fsChannel, &dtmf) == SWITCH_STATUS_SUCCESS) ? true : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OpalMediaFormatList FSConnection::GetMediaFormats() const
|
OpalMediaFormatList FSConnection::GetMediaFormats() const
|
||||||
@ -697,37 +707,37 @@ void FSConnection::SetCodecs()
|
|||||||
{
|
{
|
||||||
int numCodecs = 0;
|
int numCodecs = 0;
|
||||||
const switch_codec_implementation_t *codecs[SWITCH_MAX_CODECS];
|
const switch_codec_implementation_t *codecs[SWITCH_MAX_CODECS];
|
||||||
const char *codec_string = NULL, *abs, *ocodec;
|
const char *codec_string = NULL, *abs, *ocodec;
|
||||||
char *tmp_codec_string = NULL;
|
char *tmp_codec_string = NULL;
|
||||||
char *codec_order[SWITCH_MAX_CODECS];
|
char *codec_order[SWITCH_MAX_CODECS];
|
||||||
int codec_order_last;
|
int codec_order_last;
|
||||||
|
|
||||||
|
|
||||||
if ((abs = switch_channel_get_variable(m_fsChannel, "absolute_codec_string"))) {
|
if ((abs = switch_channel_get_variable(m_fsChannel, "absolute_codec_string"))) {
|
||||||
codec_string = abs;
|
codec_string = abs;
|
||||||
} else {
|
} else {
|
||||||
if ((abs = switch_channel_get_variable(m_fsChannel, "codec_string"))) {
|
if ((abs = switch_channel_get_variable(m_fsChannel, "codec_string"))) {
|
||||||
codec_string = abs;
|
codec_string = abs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ocodec = switch_channel_get_variable(m_fsChannel, SWITCH_ORIGINATOR_CODEC_VARIABLE))) {
|
if ((ocodec = switch_channel_get_variable(m_fsChannel, SWITCH_ORIGINATOR_CODEC_VARIABLE))) {
|
||||||
codec_string = switch_core_session_sprintf(m_fsSession, "%s,%s", ocodec, codec_string);
|
codec_string = switch_core_session_sprintf(m_fsSession, "%s,%s", ocodec, codec_string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!codec_string) {
|
if (!codec_string) {
|
||||||
codec_string = mod_opal_globals.codec_string;
|
codec_string = mod_opal_globals.codec_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codec_string) {
|
if (codec_string) {
|
||||||
if ((tmp_codec_string = strdup(codec_string))) {
|
if ((tmp_codec_string = strdup(codec_string))) {
|
||||||
codec_order_last = switch_separate_string(tmp_codec_string, ',', codec_order, SWITCH_MAX_CODECS);
|
codec_order_last = switch_separate_string(tmp_codec_string, ',', codec_order, SWITCH_MAX_CODECS);
|
||||||
numCodecs = switch_loadable_module_get_codecs_sorted(codecs, SWITCH_MAX_CODECS, codec_order, codec_order_last);
|
numCodecs = switch_loadable_module_get_codecs_sorted(codecs, SWITCH_MAX_CODECS, codec_order, codec_order_last);
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
numCodecs = switch_loadable_module_get_codecs(codecs, sizeof(codecs) / sizeof(codecs[0]));
|
numCodecs = switch_loadable_module_get_codecs(codecs, sizeof(codecs) / sizeof(codecs[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < numCodecs; i++) {
|
for (int i = 0; i < numCodecs; i++) {
|
||||||
const switch_codec_implementation_t *codec = codecs[i];
|
const switch_codec_implementation_t *codec = codecs[i];
|
||||||
@ -845,32 +855,32 @@ static switch_status_t on_destroy(switch_core_session_t *session)
|
|||||||
opal_private_t *tech_pvt = (opal_private_t *) switch_core_session_get_private(session);
|
opal_private_t *tech_pvt = (opal_private_t *) switch_core_session_get_private(session);
|
||||||
|
|
||||||
if (tech_pvt) {
|
if (tech_pvt) {
|
||||||
if (tech_pvt->read_codec.implementation) {
|
if (tech_pvt->read_codec.implementation) {
|
||||||
switch_core_codec_destroy(&tech_pvt->read_codec);
|
switch_core_codec_destroy(&tech_pvt->read_codec);
|
||||||
}
|
|
||||||
|
|
||||||
if (tech_pvt->write_codec.implementation) {
|
|
||||||
switch_core_codec_destroy(&tech_pvt->write_codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tech_pvt->vid_read_codec.implementation) {
|
|
||||||
switch_core_codec_destroy(&tech_pvt->vid_read_codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tech_pvt->vid_write_codec.implementation) {
|
|
||||||
switch_core_codec_destroy(&tech_pvt->vid_write_codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tech_pvt->read_timer.timer_interface) {
|
|
||||||
switch_core_timer_destroy(&tech_pvt->read_timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tech_pvt->vid_read_timer.timer_interface) {
|
|
||||||
switch_core_timer_destroy(&tech_pvt->vid_read_timer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
if (tech_pvt->write_codec.implementation) {
|
||||||
|
switch_core_codec_destroy(&tech_pvt->write_codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tech_pvt->vid_read_codec.implementation) {
|
||||||
|
switch_core_codec_destroy(&tech_pvt->vid_read_codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tech_pvt->vid_write_codec.implementation) {
|
||||||
|
switch_core_codec_destroy(&tech_pvt->vid_write_codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tech_pvt->read_timer.timer_interface) {
|
||||||
|
switch_core_timer_destroy(&tech_pvt->read_timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tech_pvt->vid_read_timer.timer_interface) {
|
||||||
|
switch_core_timer_destroy(&tech_pvt->vid_read_timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this function has to be called with the original session beause the FSConnection might already be destroyed and we
|
/* this function has to be called with the original session beause the FSConnection might already be destroyed and we
|
||||||
@ -974,6 +984,14 @@ switch_status_t FSConnection::receive_message(switch_core_session_message_t *msg
|
|||||||
OnAlerting();
|
OnAlerting();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SWITCH_MESSAGE_INDICATE_DEFLECT:
|
||||||
|
{
|
||||||
|
char transfer_to[128] = "";
|
||||||
|
switch_set_string(transfer_to, msg->string_arg);
|
||||||
|
TransferConnection(transfer_to);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case SWITCH_MESSAGE_INDICATE_PROGRESS:
|
case SWITCH_MESSAGE_INDICATE_PROGRESS:
|
||||||
case SWITCH_MESSAGE_INDICATE_ANSWER:
|
case SWITCH_MESSAGE_INDICATE_ANSWER:
|
||||||
{
|
{
|
||||||
@ -1163,6 +1181,7 @@ PBoolean FSMediaStream::Open()
|
|||||||
m_switchCodec->implementation->samples_per_packet,
|
m_switchCodec->implementation->samples_per_packet,
|
||||||
switch_core_session_get_pool(m_fsSession)) != SWITCH_STATUS_SUCCESS) {
|
switch_core_session_get_pool(m_fsSession)) != SWITCH_STATUS_SUCCESS) {
|
||||||
switch_core_codec_destroy(m_switchCodec);
|
switch_core_codec_destroy(m_switchCodec);
|
||||||
|
m_switchCodec = NULL;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1190,6 +1209,11 @@ PBoolean FSMediaStream::Close()
|
|||||||
if (!IsOpen())
|
if (!IsOpen())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
/* forget these FS will properly destroy them for us */
|
||||||
|
|
||||||
|
m_switchTimer = NULL;
|
||||||
|
m_switchCodec = NULL;
|
||||||
|
|
||||||
return OpalMediaStream::Close();
|
return OpalMediaStream::Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1205,6 +1229,21 @@ PBoolean FSMediaStream::RequiresPatchThread(OpalMediaStream *) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FSMediaStream::CheckPatchAndLock()
|
||||||
|
{
|
||||||
|
if (GetConnection().GetPhase() >= GetConnection().ReleasingPhase || !IsOpen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (LockReadWrite()) {
|
||||||
|
if (!GetPatch() || !IsOpen()) {
|
||||||
|
UnlockReadWrite();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch_status_t FSMediaStream::read_frame(switch_frame_t **frame, switch_io_flag_t flags)
|
switch_status_t FSMediaStream::read_frame(switch_frame_t **frame, switch_io_flag_t flags)
|
||||||
{
|
{
|
||||||
@ -1222,15 +1261,26 @@ switch_status_t FSMediaStream::read_frame(switch_frame_t **frame, switch_io_flag
|
|||||||
while(!GetPatch()) {
|
while(!GetPatch()) {
|
||||||
switch_cond_next();
|
switch_cond_next();
|
||||||
}
|
}
|
||||||
GetPatch()->OnPatchStart();
|
if (CheckPatchAndLock()) {
|
||||||
m_callOnStart = false;
|
GetPatch()->OnPatchStart();
|
||||||
|
m_callOnStart = false;
|
||||||
|
UnlockReadWrite();
|
||||||
|
} else {
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_readFrame.flags = 0;
|
m_readFrame.flags = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
while (switch_channel_ready(m_fsChannel)) {
|
while (switch_channel_ready(m_fsChannel)) {
|
||||||
if (!GetPatch()->GetSource().ReadPacket(m_readRTP)) {
|
if (CheckPatchAndLock()) {
|
||||||
|
if (!GetPatch()->GetSource().ReadPacket(m_readRTP)) {
|
||||||
|
UnlockReadWrite();
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
|
UnlockReadWrite();
|
||||||
|
} else {
|
||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1249,9 +1299,16 @@ switch_status_t FSMediaStream::read_frame(switch_frame_t **frame, switch_io_flag
|
|||||||
if (switch_channel_test_private_flag(m_fsChannel, CF_NEED_FLUSH)) {
|
if (switch_channel_test_private_flag(m_fsChannel, CF_NEED_FLUSH)) {
|
||||||
switch_channel_clear_private_flag(m_fsChannel, CF_NEED_FLUSH);
|
switch_channel_clear_private_flag(m_fsChannel, CF_NEED_FLUSH);
|
||||||
for(;;) {
|
for(;;) {
|
||||||
if (!GetPatch()->GetSource().ReadPacket(m_readRTP)) {
|
if (CheckPatchAndLock()) {
|
||||||
|
if (!GetPatch()->GetSource().ReadPacket(m_readRTP)) {
|
||||||
|
UnlockReadWrite();
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
|
UnlockReadWrite();
|
||||||
|
} else {
|
||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_readRTP.GetPayloadSize()) {
|
if (!m_readRTP.GetPayloadSize()) {
|
||||||
m_readFrame.flags = SFF_CNG;
|
m_readFrame.flags = SFF_CNG;
|
||||||
break;
|
break;
|
||||||
@ -1259,7 +1316,13 @@ switch_status_t FSMediaStream::read_frame(switch_frame_t **frame, switch_io_flag
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (!m_switchTimer || !GetPatch()->GetSource().ReadPacket(m_readRTP)) {
|
if (CheckPatchAndLock()) {
|
||||||
|
if (!m_switchTimer || !GetPatch()->GetSource().ReadPacket(m_readRTP)) {
|
||||||
|
UnlockReadWrite();
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
|
UnlockReadWrite();
|
||||||
|
} else {
|
||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1303,8 +1366,13 @@ switch_status_t FSMediaStream::write_frame(const switch_frame_t *frame, switch_i
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_callOnStart) {
|
if (m_callOnStart) {
|
||||||
GetPatch()->OnPatchStart();
|
if (CheckPatchAndLock()) {
|
||||||
m_callOnStart = false;
|
GetPatch()->OnPatchStart();
|
||||||
|
m_callOnStart = false;
|
||||||
|
UnlockReadWrite();
|
||||||
|
} else {
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((frame->flags & SFF_CNG)) {
|
if ((frame->flags & SFF_CNG)) {
|
||||||
@ -1313,8 +1381,15 @@ switch_status_t FSMediaStream::write_frame(const switch_frame_t *frame, switch_i
|
|||||||
|
|
||||||
if ((frame->flags & SFF_RAW_RTP) != 0) {
|
if ((frame->flags & SFF_RAW_RTP) != 0) {
|
||||||
RTP_DataFrame rtp((const BYTE *) frame->packet, frame->packetlen, false);
|
RTP_DataFrame rtp((const BYTE *) frame->packet, frame->packetlen, false);
|
||||||
if (GetPatch()->PushFrame(rtp)) {
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
if (CheckPatchAndLock()) {
|
||||||
|
if (GetPatch()->PushFrame(rtp)) {
|
||||||
|
UnlockReadWrite();
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
UnlockReadWrite();
|
||||||
|
} else {
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1337,8 +1412,14 @@ switch_status_t FSMediaStream::write_frame(const switch_frame_t *frame, switch_i
|
|||||||
|
|
||||||
memcpy(rtp.GetPayloadPtr(), frame->data, frame->datalen);
|
memcpy(rtp.GetPayloadPtr(), frame->data, frame->datalen);
|
||||||
|
|
||||||
if (GetPatch()->PushFrame(rtp)) {
|
if (CheckPatchAndLock()) {
|
||||||
return SWITCH_STATUS_SUCCESS;
|
if (GetPatch()->PushFrame(rtp)) {
|
||||||
|
UnlockReadWrite();
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
UnlockReadWrite();
|
||||||
|
} else {
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,7 +177,11 @@ class FSConnection:public OpalLocalConnection {
|
|||||||
PCLASSINFO(FSConnection, OpalLocalConnection)
|
PCLASSINFO(FSConnection, OpalLocalConnection)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FSConnection(OpalCall & call, FSEndPoint & endpoint, switch_caller_profile_t *outbound_profile);
|
FSConnection(OpalCall & call,
|
||||||
|
FSEndPoint & endpoint,
|
||||||
|
switch_caller_profile_t *outbound_profile,
|
||||||
|
switch_core_session_t *fsSession,
|
||||||
|
switch_channel_t *fsChannel);
|
||||||
|
|
||||||
virtual bool OnIncoming();
|
virtual bool OnIncoming();
|
||||||
virtual void OnReleased();
|
virtual void OnReleased();
|
||||||
@ -194,7 +198,6 @@ class FSConnection:public OpalLocalConnection {
|
|||||||
DECLARE_CALLBACK0(on_init);
|
DECLARE_CALLBACK0(on_init);
|
||||||
DECLARE_CALLBACK0(on_routing);
|
DECLARE_CALLBACK0(on_routing);
|
||||||
DECLARE_CALLBACK0(on_execute);
|
DECLARE_CALLBACK0(on_execute);
|
||||||
//DECLARE_CALLBACK0(on_hangup);
|
|
||||||
|
|
||||||
DECLARE_CALLBACK0(on_exchange_media);
|
DECLARE_CALLBACK0(on_exchange_media);
|
||||||
DECLARE_CALLBACK0(on_soft_execute);
|
DECLARE_CALLBACK0(on_soft_execute);
|
||||||
@ -251,6 +254,8 @@ class FSMediaStream:public OpalMediaStream {
|
|||||||
RTP_DataFrame m_readRTP;
|
RTP_DataFrame m_readRTP;
|
||||||
bool m_callOnStart;
|
bool m_callOnStart;
|
||||||
uint32_t m_timeStamp;
|
uint32_t m_timeStamp;
|
||||||
|
|
||||||
|
bool CheckPatchAndLock();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user