core/EventManager.cpp
author Scott Ehlert <ds@alliedmods.net>
Sun Aug 24 04:35:48 2008 +0000 (2008-08-24)
changeset 2237 4403a982ec91
parent 2106 b051de4afc41
child 2289 44045ead61cd
permissions -rw-r--r--
Improved bot detection slightly. This was meant to go along with the fix for amb1801.
ds@1952
     1
/**
ds@1952
     2
 * vim: set ts=4 :
ds@1952
     3
 * =============================================================================
ds@1952
     4
 * SourceMod
ds@1952
     5
 * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
ds@1952
     6
 * =============================================================================
ds@1952
     7
 *
ds@1952
     8
 * This program is free software; you can redistribute it and/or modify it under
ds@1952
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
ds@1952
    10
 * Free Software Foundation.
ds@1952
    11
 * 
ds@1952
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
ds@1952
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
ds@1952
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
ds@1952
    15
 * details.
ds@1952
    16
 *
ds@1952
    17
 * You should have received a copy of the GNU General Public License along with
ds@1952
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
ds@1952
    19
 *
ds@1952
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
ds@1952
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
ds@1952
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
ds@1952
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
ds@1952
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
ds@1952
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
ds@1952
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
ds@1952
    27
 * or <http://www.sourcemod.net/license.php>.
ds@1952
    28
 *
ds@1952
    29
 * Version: $Id$
ds@1952
    30
 */
ds@1952
    31
ds@1952
    32
#include "EventManager.h"
ds@1952
    33
#include "ForwardSys.h"
ds@1952
    34
#include "HandleSys.h"
ds@1952
    35
#include "PluginSys.h"
ds@1952
    36
#include "sm_stringutil.h"
ds@1952
    37
ds@1952
    38
EventManager g_EventManager;
ds@1952
    39
ds@1952
    40
SH_DECL_HOOK2(IGameEventManager2, FireEvent, SH_NOATTRIB, 0, bool, IGameEvent *, bool);
ds@1952
    41
ds@1952
    42
const ParamType GAMEEVENT_PARAMS[] = {Param_Cell, Param_String, Param_Cell};
ds@1952
    43
typedef List<EventHook *> EventHookList;
ds@1952
    44
ds@1952
    45
EventManager::EventManager() : m_EventType(0)
ds@1952
    46
{
ds@1952
    47
	/* Create an event lookup trie */
ds@1952
    48
	m_EventHooks = sm_trie_create();
ds@1952
    49
}
ds@1952
    50
ds@1952
    51
EventManager::~EventManager()
ds@1952
    52
{
ds@1952
    53
	sm_trie_destroy(m_EventHooks);
ds@1952
    54
ds@1952
    55
	/* Free memory used by EventInfo structs if any */
ds@1952
    56
	CStack<EventInfo *>::iterator iter;
ds@1952
    57
	for (iter = m_FreeEvents.begin(); iter != m_FreeEvents.end(); iter++)
ds@1952
    58
	{
ds@1952
    59
		delete (*iter);
ds@1952
    60
	}
ds@1952
    61
ds@1952
    62
	m_FreeEvents.popall();
ds@1952
    63
}
ds@1952
    64
ds@1952
    65
void EventManager::OnSourceModAllInitialized()
ds@1952
    66
{
ds@1952
    67
	/* Add a hook for IGameEventManager2::FireEvent() */
ds@1952
    68
	SH_ADD_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent, false);
ds@1952
    69
	SH_ADD_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent_Post, true);
ds@1952
    70
ds@1952
    71
	HandleAccess sec;
ds@1952
    72
ds@1952
    73
	/* Handle access security for 'GameEvent' handle type */
ds@1952
    74
	sec.access[HandleAccess_Read] = 0;
ds@1952
    75
	sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
ds@1952
    76
	sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
ds@1952
    77
ds@1952
    78
	/* Create the 'GameEvent' handle type */
ds@1952
    79
	m_EventType = g_HandleSys.CreateType("GameEvent", this, 0, NULL, &sec, g_pCoreIdent, NULL);
ds@1952
    80
}
ds@1952
    81
ds@1952
    82
void EventManager::OnSourceModShutdown()
ds@1952
    83
{
ds@1952
    84
	/* Remove hook for IGameEventManager2::FireEvent() */
ds@1952
    85
	SH_REMOVE_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent, false);
ds@1952
    86
	SH_REMOVE_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent_Post, true);
ds@1952
    87
ds@1952
    88
	/* Remove the 'GameEvent' handle type */
ds@1952
    89
	g_HandleSys.RemoveType(m_EventType, g_pCoreIdent);
ds@1952
    90
ds@1952
    91
	/* Remove ourselves as listener for events */
ds@1952
    92
	gameevents->RemoveListener(this);
ds@1952
    93
}
ds@1952
    94
ds@1952
    95
void EventManager::OnHandleDestroy(HandleType_t type, void *object)
ds@1952
    96
{
ds@1952
    97
	EventInfo *pInfo = static_cast<EventInfo *>(object);
ds@1952
    98
ds@1952
    99
	/* Should only free event when created by a plugin */
ds@1952
   100
	if (pInfo->pOwner)
ds@1952
   101
	{
ds@1952
   102
		/* Free IGameEvent */
ds@1952
   103
		gameevents->FreeEvent(pInfo->pEvent);
ds@1952
   104
ds@1952
   105
		/* Add EventInfo struct to free event stack */
ds@1952
   106
		m_FreeEvents.push(pInfo);
ds@1952
   107
	}
ds@1952
   108
}
ds@1952
   109
ds@1952
   110
void EventManager::OnPluginUnloaded(IPlugin *plugin)
ds@1952
   111
{
ds@1952
   112
	EventHookList *pHookList;
ds@1952
   113
	EventHookList::iterator iter;
ds@1952
   114
	EventHook *pHook;
ds@1952
   115
ds@1952
   116
	// If plugin has an event hook list...
ds@1952
   117
	if (plugin->GetProperty("EventHooks", reinterpret_cast<void **>(&pHookList), true))
ds@1952
   118
	{
ds@1952
   119
		for (iter = pHookList->begin(); iter != pHookList->end(); iter++)
ds@1952
   120
		{
ds@1952
   121
			pHook = (*iter);
ds@1952
   122
ds@1952
   123
			if (--pHook->refCount == 0)
ds@1952
   124
			{
ds@1952
   125
				if (pHook->pPreHook)
ds@1952
   126
				{
ds@1952
   127
					g_Forwards.ReleaseForward(pHook->pPreHook);
ds@1952
   128
				}
ds@1952
   129
ds@1952
   130
				if (pHook->pPostHook)
ds@1952
   131
				{
ds@1952
   132
					g_Forwards.ReleaseForward(pHook->pPostHook);
ds@1952
   133
				}
ds@1952
   134
ds@1952
   135
				delete pHook;
ds@1952
   136
			}
ds@1952
   137
		}
ds@1952
   138
ds@1952
   139
		delete pHookList;
ds@1952
   140
	}
ds@1952
   141
}
ds@1952
   142
ds@1952
   143
/* IGameEventListener2::FireGameEvent */
ds@1952
   144
void EventManager::FireGameEvent(IGameEvent *pEvent)
ds@1952
   145
{
ds@1952
   146
	/* Not going to do anything here.
ds@1952
   147
	   Just need to add ourselves as a listener to make our hook on IGameEventManager2::FireEvent work */
ds@1952
   148
}
ds@1952
   149
ds@1952
   150
EventHookError EventManager::HookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
ds@1952
   151
{
ds@1952
   152
	EventHook *pHook;
ds@1952
   153
ds@1952
   154
	/* If we aren't listening to this event... */
ds@1952
   155
	if (!gameevents->FindListener(this, name))
ds@1952
   156
	{
ds@1952
   157
		/* Then add ourselves */
ds@1952
   158
		if (!gameevents->AddListener(this, name, true))
ds@1952
   159
		{
ds@1952
   160
			/* If event doesn't exist... */
ds@1952
   161
			return EventHookErr_InvalidEvent;
ds@1952
   162
		}
ds@1952
   163
	}
ds@1952
   164
ds@1952
   165
	/* If a hook structure does not exist... */
ds@1952
   166
	if (!sm_trie_retrieve(m_EventHooks, name, (void **)&pHook))
ds@1952
   167
	{
ds@1952
   168
		EventHookList *pHookList;
ds@1952
   169
		IPlugin *plugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
ds@1952
   170
ds@1952
   171
		/* Check plugin for an existing EventHook list */
ds@1952
   172
		if (!plugin->GetProperty("EventHooks", (void **)&pHookList))
ds@1952
   173
		{
ds@1952
   174
			pHookList = new EventHookList();
ds@1952
   175
			plugin->SetProperty("EventHooks", pHookList);
ds@1952
   176
		}
ds@1952
   177
ds@1952
   178
		/* Create new GameEventHook structure */
ds@1952
   179
		pHook = new EventHook();
ds@1952
   180
ds@1952
   181
		if (mode == EventHookMode_Pre)
ds@1952
   182
		{
ds@1952
   183
			/* Create forward for a pre hook */
ds@1952
   184
			pHook->pPreHook = g_Forwards.CreateForwardEx(NULL, ET_Hook, 3, GAMEEVENT_PARAMS);
ds@1952
   185
			/* Add to forward list */
ds@1952
   186
			pHook->pPreHook->AddFunction(pFunction);
ds@1952
   187
		} else {
ds@1952
   188
			/* Create forward for a post hook */
ds@1952
   189
			pHook->pPostHook = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
ds@1952
   190
			/* Should we copy data from a pre hook to the post hook? */
ds@1952
   191
			pHook->postCopy = (mode == EventHookMode_Post);
ds@1952
   192
			/* Add to forward list */
ds@1952
   193
			pHook->pPostHook->AddFunction(pFunction);
ds@1952
   194
		}
ds@1952
   195
ds@1952
   196
		/* Increase reference count */
ds@1952
   197
		pHook->refCount++;
ds@1952
   198
ds@1952
   199
		/* Add hook structure to hook lists */
ds@1952
   200
		pHookList->push_back(pHook);
ds@1952
   201
		sm_trie_insert(m_EventHooks, name, pHook);
ds@1952
   202
ds@1952
   203
		return EventHookErr_Okay;
ds@1952
   204
	}
ds@1952
   205
ds@1952
   206
	/* Hook structure already exists at this point */
ds@1952
   207
ds@1952
   208
	if (mode == EventHookMode_Pre)
ds@1952
   209
	{
ds@1952
   210
		/* Create pre hook forward if necessary */
ds@1952
   211
		if (!pHook->pPreHook)
ds@1952
   212
		{
ds@1952
   213
			pHook->pPreHook = g_Forwards.CreateForwardEx(NULL, ET_Event, 3, GAMEEVENT_PARAMS);
ds@1952
   214
		}
ds@1952
   215
ds@1952
   216
		/* Add plugin function to forward list */
ds@1952
   217
		pHook->pPreHook->AddFunction(pFunction);
ds@1952
   218
	} else {
ds@1952
   219
		/* Create post hook forward if necessary */
ds@1952
   220
		if (!pHook->pPostHook)
ds@1952
   221
		{
ds@1952
   222
			pHook->pPostHook = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
ds@1952
   223
		}
ds@1952
   224
ds@1952
   225
		/* If postCopy is false, then we may want to set it to true */
ds@1952
   226
		if (!pHook->postCopy)
ds@1952
   227
		{
ds@1952
   228
			pHook->postCopy = (mode == EventHookMode_Post);
ds@1952
   229
		}
ds@1952
   230
ds@1952
   231
		/* Add plugin function to forward list */
ds@1952
   232
		pHook->pPostHook->AddFunction(pFunction);
ds@1952
   233
	}
ds@1952
   234
ds@1952
   235
	/* Increase reference count */
ds@1952
   236
	pHook->refCount++;
ds@1952
   237
ds@1952
   238
	return EventHookErr_Okay;
ds@1952
   239
}
ds@1952
   240
ds@1952
   241
EventHookError EventManager::UnhookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
ds@1952
   242
{
ds@1952
   243
	EventHook *pHook;
ds@1952
   244
	IChangeableForward **pEventForward;
ds@1952
   245
ds@1952
   246
	/* If hook does not exist at all */
ds@1952
   247
	if (!sm_trie_retrieve(m_EventHooks, name, (void **)&pHook))
ds@1952
   248
	{
ds@1952
   249
		return EventHookErr_NotActive;
ds@1952
   250
	}
ds@1952
   251
ds@1952
   252
	/* One forward to rule them all */
ds@1952
   253
	if (mode == EventHookMode_Pre)
ds@1952
   254
	{
ds@1952
   255
		pEventForward = &pHook->pPreHook;
ds@1952
   256
	} else {
ds@1952
   257
		pEventForward = &pHook->pPostHook;
ds@1952
   258
	}
ds@1952
   259
ds@1952
   260
	/* Remove function from forward's list */
ds@1952
   261
	if (*pEventForward == NULL || !(*pEventForward)->RemoveFunction(pFunction))
ds@1952
   262
	{
ds@1952
   263
		return EventHookErr_InvalidCallback;
ds@1952
   264
	}
ds@1952
   265
ds@1952
   266
	/* If forward's list contains 0 functions now, free it */
ds@1952
   267
	if ((*pEventForward)->GetFunctionCount() == 0)
ds@1952
   268
	{
ds@1952
   269
		g_Forwards.ReleaseForward(*pEventForward);
ds@1952
   270
		*pEventForward = NULL;
ds@1952
   271
	}
ds@1952
   272
ds@1952
   273
	/* Decrement reference count */
ds@1952
   274
	if (--pHook->refCount == 0)
ds@1952
   275
	{
ds@1952
   276
		/* If reference count is now 0, free hook structure */
ds@1952
   277
ds@1952
   278
		EventHookList *pHookList;
ds@1952
   279
		IPlugin *plugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
ds@1952
   280
ds@1952
   281
		/* Get plugin's event hook list */
ds@1952
   282
		if (!plugin->GetProperty("EventHooks", (void**)&pHookList))
ds@1952
   283
		{
ds@1952
   284
			return EventHookErr_NotActive;
ds@1952
   285
		}
ds@1952
   286
ds@1952
   287
		/* Make sure the event was actually being hooked */
ds@1952
   288
		if (pHookList->find(pHook) == pHookList->end())
ds@1952
   289
		{
ds@1952
   290
			return EventHookErr_NotActive;
ds@1952
   291
		}
ds@1952
   292
ds@1952
   293
		/* Remove current structure from plugin's list */
ds@1952
   294
		pHookList->remove(pHook);
ds@1952
   295
ds@1952
   296
		/* Delete entry in trie */
ds@1952
   297
		sm_trie_delete(m_EventHooks, name);
ds@1952
   298
ds@1952
   299
		/* And finally free structure memory */
ds@1952
   300
		delete pHook;
ds@1952
   301
	}
ds@1952
   302
ds@1952
   303
	return EventHookErr_Okay;
ds@1952
   304
}
ds@1952
   305
ds@1952
   306
EventInfo *EventManager::CreateEvent(IPluginContext *pContext, const char *name, bool force)
ds@1952
   307
{
ds@1952
   308
	EventInfo *pInfo;
ds@1952
   309
	IGameEvent *pEvent = gameevents->CreateEvent(name, force);
ds@1952
   310
ds@1952
   311
	if (pEvent)
ds@1952
   312
	{
ds@1952
   313
		if (m_FreeEvents.empty())
ds@1952
   314
		{
ds@1952
   315
			pInfo = new EventInfo();
ds@1952
   316
		} else {
ds@1952
   317
			pInfo = m_FreeEvents.front();
ds@1952
   318
			m_FreeEvents.pop();
ds@1952
   319
		}
ds@1952
   320
ds@1952
   321
ds@1952
   322
		pInfo->pEvent = pEvent;
ds@1952
   323
		pInfo->pOwner = pContext->GetIdentity();
ds@1952
   324
ds@1952
   325
		return pInfo;
ds@1952
   326
	}
ds@1952
   327
ds@1952
   328
	return NULL;
ds@1952
   329
}
ds@1952
   330
ds@1952
   331
void EventManager::FireEvent(EventInfo *pInfo, bool bDontBroadcast)
ds@1952
   332
{
ds@1952
   333
	/* Actually fire event now */
ds@1952
   334
	gameevents->FireEvent(pInfo->pEvent, bDontBroadcast);
ds@1952
   335
ds@1952
   336
	/* IGameEvent is free at this point, so no one owns this */
ds@1952
   337
	pInfo->pOwner = NULL;
ds@1952
   338
ds@1952
   339
	/* Add EventInfo struct to free event stack */
ds@1952
   340
	m_FreeEvents.push(pInfo);
ds@1952
   341
}
ds@1952
   342
ds@1952
   343
void EventManager::CancelCreatedEvent(EventInfo *pInfo)
ds@1952
   344
{
ds@1952
   345
	/* Free event from IGameEventManager2 */
ds@1952
   346
	gameevents->FreeEvent(pInfo->pEvent);
ds@1952
   347
ds@1952
   348
	/* IGameEvent is free at this point, so no one owns this */
ds@1952
   349
	pInfo->pOwner = NULL;
ds@1952
   350
ds@1952
   351
	/* Add EventInfo struct to free event stack */
ds@1952
   352
	m_FreeEvents.push(pInfo);
ds@1952
   353
}
ds@1952
   354
ds@1952
   355
/* IGameEventManager2::FireEvent hook */
ds@1952
   356
bool EventManager::OnFireEvent(IGameEvent *pEvent, bool bDontBroadcast)
ds@1952
   357
{
ds@1952
   358
	EventHook *pHook;
ds@1952
   359
	IChangeableForward *pForward;
ds@1952
   360
	const char *name;
ds@1952
   361
	cell_t res = Pl_Continue;
ds@2106
   362
ds@2106
   363
	/* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
ds@2106
   364
	if (!pEvent)
ds@2106
   365
	{
ds@2106
   366
		RETURN_META_VALUE(MRES_IGNORED, false);
ds@2106
   367
	}
ds@1952
   368
	
ds@1952
   369
	/* Get the event name, we're going to need this for passing to post hooks */
ds@1952
   370
	name = pEvent->GetName();
ds@1952
   371
ds@1952
   372
	m_EventNames.push(name);
ds@1952
   373
ds@1952
   374
	if (sm_trie_retrieve(m_EventHooks, name, reinterpret_cast<void **>(&pHook)))
ds@1952
   375
	{
ds@1952
   376
		pForward = pHook->pPreHook;
ds@1952
   377
ds@1952
   378
		if (pForward)
ds@1952
   379
		{
ds@1952
   380
			EventInfo info = { pEvent, NULL };
ds@1952
   381
ds@1952
   382
			Handle_t hndl = g_HandleSys.CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
ds@1952
   383
			pForward->PushCell(hndl);
ds@1952
   384
			pForward->PushString(name);
ds@1952
   385
			pForward->PushCell(bDontBroadcast);
ds@1952
   386
			pForward->Execute(&res, NULL);
ds@1952
   387
ds@1952
   388
			HandleSecurity sec(NULL, g_pCoreIdent);
ds@1952
   389
			g_HandleSys.FreeHandle(hndl, &sec);
ds@1952
   390
		}
ds@1952
   391
ds@1952
   392
		if (pHook->postCopy)
ds@1952
   393
		{
ds@1952
   394
			pHook->pEventCopy = gameevents->DuplicateEvent(pEvent);
ds@1952
   395
		}
ds@1952
   396
ds@1952
   397
		if (res)
ds@1952
   398
		{
ds@1952
   399
			gameevents->FreeEvent(pEvent);
ds@1952
   400
			RETURN_META_VALUE(MRES_SUPERCEDE, false);
ds@1952
   401
		}
ds@1952
   402
	}
ds@1952
   403
ds@1952
   404
	RETURN_META_VALUE(MRES_IGNORED, true);
ds@1952
   405
}
ds@1952
   406
ds@1952
   407
/* IGameEventManager2::FireEvent post hook */
ds@1952
   408
bool EventManager::OnFireEvent_Post(IGameEvent *pEvent, bool bDontBroadcast)
ds@1952
   409
{
ds@1952
   410
	IGameEvent *pEventCopy = NULL;
ds@1952
   411
	EventHook *pHook;
ds@1952
   412
	IChangeableForward *pForward;
ds@1952
   413
	const char *name;
ds@1952
   414
	Handle_t hndl = 0;
ds@1952
   415
ds@2106
   416
	/* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
ds@2106
   417
	if (!pEvent)
ds@2106
   418
	{
ds@2106
   419
		RETURN_META_VALUE(MRES_IGNORED, false);
ds@2106
   420
	}
ds@2106
   421
ds@1952
   422
	name = m_EventNames.front();
ds@1952
   423
ds@1952
   424
	if (sm_trie_retrieve(m_EventHooks, name, reinterpret_cast<void **>(&pHook)))
ds@1952
   425
	{
ds@1952
   426
		pForward = pHook->pPostHook;
ds@1952
   427
ds@1952
   428
		if (pForward)
ds@1952
   429
		{
ds@1952
   430
			EventInfo info = { pHook->pEventCopy, NULL };
ds@1952
   431
ds@1952
   432
			if (pHook->postCopy)
ds@1952
   433
			{
ds@1952
   434
				hndl = g_HandleSys.CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
ds@1952
   435
ds@1952
   436
				pForward->PushCell(hndl);
ds@1952
   437
			} else {
ds@1952
   438
				pForward->PushCell(BAD_HANDLE);
ds@1952
   439
			}
ds@1952
   440
ds@1952
   441
			pForward->PushString(name);
ds@1952
   442
			pForward->PushCell(bDontBroadcast);
ds@1952
   443
			pForward->Execute(NULL);
ds@1952
   444
ds@1952
   445
			if (pHook->postCopy)
ds@1952
   446
			{
ds@1952
   447
				/* Free handle */
ds@1952
   448
				HandleSecurity sec(NULL, g_pCoreIdent);
ds@1952
   449
				g_HandleSys.FreeHandle(hndl, &sec);
ds@1952
   450
ds@1952
   451
				/* Free event structure */
ds@1952
   452
				gameevents->FreeEvent(pEventCopy);
ds@1952
   453
				pHook->pEventCopy = NULL;
ds@1952
   454
			}
ds@1952
   455
		}
ds@1952
   456
	}
ds@1952
   457
ds@1952
   458
	m_EventNames.pop();
ds@1952
   459
ds@1952
   460
	RETURN_META_VALUE(MRES_IGNORED, true);
ds@1952
   461
}