Files
GTASource/game/security/plugins/rtmaplugin.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

670 lines
21 KiB
C++

#include "security/ragesecengine.h"
#if USE_RAGESEC
#if RSG_PC
#include <wchar.h>
#endif
//Rage headers
#include "string/stringhash.h"
//Framework headers
#include "security/ragesecwinapi.h"
#include "system/threadtype.h"
//Game headers
#include "Network/Live/NetworkTelemetry.h"
#include "Network/NetworkInterface.h"
#include "Network/Events/NetworkEventTypes.h"
#include "Network/General/NetworkAssetVerifier.h"
#include "Network/Sessions/NetworkSession.h"
#include "script/script.h"
#include "system/InfoState.h"
#include "system/memorycheck.h"
#include "streaming/defragmentation.h"
#include "stats/StatsInterface.h"
// Security headers
#include "net/status.h"
#include "net/task.h"
#include "security/ragesecplugin.h"
#include "security/ragesecenginetasks.h"
#include "script/thread.h"
#include "security/plugins/rtmaplugin.h"
#include "security/ragesechashutility.h"
#include "security/ragesecgameinterface.h"
using namespace rage;
#if RAGE_SEC_TASK_RTMA
// #defines
#define RTMA_PERFORMANCE_MS_THRESHOLD 20
#define RTMA_PAGES_TO_CHECK 256
#define RTMA_POLLING_MS 15*1000
#define RTMA_ID 0x6F996781
#define RTMA_SIZE_OF_KB sizeof(unsigned char)*1024
#if RSG_PC
#define RTMA_SIZE_OF_PAGE RTMA_SIZE_OF_KB * 4 // Pages indicated as 4 KB according to MSDN (aside from weird exceptions).
#elif RSG_ORBIS
#define RTMA_SIZE_OF_PAGE RTMA_SIZE_OF_KB * 4 * 4
#endif
#if RSG_PC
static int sm_maxRegionSizeToCheck = 0x10000;
#endif
static int sm_nextCRCIndex = 0;
static const size_t sm_maxFailedCRCs = 32;
static sysCriticalSectionToken m_cs;
static u32 sm_trashValue = 0;
#if RSG_PC || RSG_ORBIS
class MemoryRegionCheckFn
{
public:
MemoryRegionCheckFn() : m_numProcessed(0), m_pMem(NULL), m_memConstraintFlags(0) {}
void Reset()
{
m_numProcessed = 0;
m_pMem = NULL;
}
#if RSG_PC
virtual bool Check(MEMORY_BASIC_INFORMATION& info, bool& failed) = 0;
DWORD m_memConstraintFlags;
#elif RSG_ORBIS
virtual bool Check(SceKernelVirtualQueryInfo& info, bool& failed) = 0;
unsigned int m_memConstraintFlags;
#endif
int m_numProcessed;
u8* m_pMem;
};
#endif
#if RSG_PC || RSG_ORBIS
// check for a string/pattern/signature in a range
// TODO: Optimize this by utilizing the PEB_LDR_DATA to locate key
// memory locations
class MemoryRegionCheckPageByteString : public MemoryRegionCheckFn
{
public:
#if RSG_PC
void Setup(u32 hash, u32 startAddr, u32 size, DWORD flags)
#elif RSG_ORBIS
void Setup(u32 hash, u32 startAddr, u32 size, unsigned int flags)
#endif
{
// Lets de-serialize the fuckery we did in MemoryHasher
// The best kind of deserializations are the hard-coded and non-abstracted ones
// Inversion from MemoryHasher
m_lowPage = startAddr & 0xFFFF;
m_highPage = startAddr >> 16;
m_sizeOfModule = (size & 0x0003FFFF);
m_length = (size & 0x00FC0000) >> 18;
m_firstByte = (size & 0xFF000000) >> 24;
m_hash = hash;
m_memConstraintFlags = flags >> ACTION_FLAG_MEM_CONSTRAINT_SHIFT;;
}
#if RSG_PC
virtual bool Check(MEMORY_BASIC_INFORMATION& info, bool& failed)
{
m_numProcessed++;
// if memory region is of matching permissions (constraint flags)
if (
(info.State == MEM_COMMIT) &&
// Check flags, that we're in commited memory
(info.Protect & m_memConstraintFlags & 0xFF) == (m_memConstraintFlags & 0xFF)
)
{
// Now since we have a basic filter, lets also check the sizes
u32 sizeInKb = m_sizeOfModule * (RTMA_SIZE_OF_KB);
u32 lowSizeThreshold = (u32)(sizeInKb *0.9);
u32 highSizeThreshold = (u32)(sizeInKb *1.1);
// Return true if we're beyond our threshold, but don't do any more processing.
// Indicate we've seen it, and we've processed it, but it doesn't apply.
if(info.RegionSize < lowSizeThreshold || info.RegionSize > highSizeThreshold){return true;}
// Now lets take our page indices, and calculate our start / end address
u64 lowPageOffset = (u64)info.BaseAddress + RTMA_SIZE_OF_PAGE * m_lowPage;
u64 highPageOffset = (u64)info.BaseAddress + RTMA_SIZE_OF_PAGE * m_highPage;
// Check to be sure that we're not starting at the last region size.
// url:bugstar:3220889
if( ((u64)info.BaseAddress + (u64)info.RegionSize) <= lowPageOffset) { return true;}
bool found = false;
// What we're doing here is going through the specified range, and looking for the
// first byte that we've specified from the mem hasher. This lets us index into
// the byte that we care about to do the hashing.
// The reason we check the m_size - mstringSize is because we don't have to
// worry about the portion of code where we correctly detect the first byte, but
// the size happens to be less than is supported in the whole length
gnetDebug3("0x%08X - [Page] Checking base address of [%016" I64FMT "X]", RTMA_ID, info.BaseAddress);
for(u64 chkstart=0; chkstart < (highPageOffset - lowPageOffset - m_length); chkstart++)
{
if(lowPageOffset+chkstart >= (u64)info.BaseAddress + (u64)info.RegionSize)
{
// No matter what the lowPageOffset + index is, we don't want to go outside the boundary
// of our current memory block.
found = false;
break;
}
// Check the indexed address against our first packed byte
if(((const u8*)lowPageOffset)[chkstart] == m_firstByte)
{
// CRC the range
u32 actualVal = crcRange((const u8*)lowPageOffset + chkstart, (const u8*)lowPageOffset + chkstart + m_length);
if(actualVal == m_hash)
{
found = true;
// Set our index to be something past the iterative loop
chkstart = highPageOffset;
}
}
}
if(found)
{
failed = true;
gnetDebug3("0x%08X - [Page] Module *did* match signature check.", RTMA_ID);
}
else
{
gnetDebug3("0x%08X - [Page] Module did not match signature check.", RTMA_ID);
}
}
return true;
}
#elif RSG_ORBIS
virtual bool Check(SceKernelVirtualQueryInfo& sceInfo, bool& failed)
{
m_numProcessed++;
// if memory region is executable and of a certain size, check for presence of string of length stringsize represented by hash
u64 regionSize = (u64)sceInfo.end - (u64)sceInfo.start;
u32 sizeInKb = m_sizeOfModule * (RTMA_SIZE_OF_KB);
u32 lowSizeThreshold = (u32)(sizeInKb *0.9);
u32 highSizeThreshold = (u32)(sizeInKb *1.1);
gnetDebug3("0x%08X - [Page] Sizes [%016x]|[%016x]", RTMA_ID, m_sizeOfModule, sizeInKb);
// Return true if we're beyond our threshold, but don't do any more processing.
// Indicate we've seen it, and we've processed it, but it doesn't apply.
if(regionSize < lowSizeThreshold || regionSize > highSizeThreshold)
{
gnetDebug3("0x%08X - [Page] Size Short Circuit [%016lx]|[%016x]|[%016x]", RTMA_ID, regionSize, lowSizeThreshold, highSizeThreshold);
return true;
}
// Now lets take our page indices, and calculate our start / end address
u64 lowPageOffset = (u64)sceInfo.start + RTMA_SIZE_OF_PAGE * m_lowPage;
u64 highPageOffset = (u64)sceInfo.start + RTMA_SIZE_OF_PAGE * m_highPage;
// Check to be sure that we're not starting at the last region size.
// url:bugstar:3220889
if( ((u64)sceInfo.start + regionSize) <= lowPageOffset) { return true;}
bool found = false;
gnetDebug3("0x%08X - [Page] Checking base address of [%p]", RTMA_ID, sceInfo.start);
for(u64 chkstart=0; chkstart < (highPageOffset - lowPageOffset - m_length); chkstart++)
{
if(lowPageOffset+chkstart >= (u64)sceInfo.end)
{
// No matter what the lowPageOffset + index is, we don't want to go outside the boundary
// of our current memory block.
found = false;
break;
}
// Check the indexed address against our first packed byte
if(((const u8*)lowPageOffset)[chkstart] == m_firstByte)
{
// CRC the range
u32 actualVal = crcRange((const u8*)lowPageOffset + chkstart, (const u8*)lowPageOffset + chkstart + m_length);
if(actualVal == m_hash)
{
found = true;
// Set our index to be something past the iterative loop
chkstart = highPageOffset;
}
}
}
if(found)
{
failed = true;
gnetDebug3("0x%08X - [Page] Module *did* match signature check.", RTMA_ID);
}
else
{
gnetDebug3("0x%08X - [Page] Module did not match signature check.", RTMA_ID);
}
return true;
}
#endif
u32 m_lowPage;
u32 m_highPage;
u32 m_length;
u32 m_sizeOfModule;
u32 m_hash;
u8 m_firstByte;
};
#endif
#if RSG_PC || RSG_ORBIS
bool EnumerateMemoryRegions(MemoryRegionCheckFn& fn, bool& failed)
{
#if RSG_PC
HANDLE process = Kernel32::GetCurrentProcess();
MEMORY_BASIC_INFORMATION info;
bool bFinished = true;
unsigned int numDelayed = 0;
gnetDebug3("0x%08X - Enumerating Memory Regions", RTMA_ID);
// For every memory region in the user space
unsigned int aggregate = 0;
while(!failed && aggregate < RTMA_PERFORMANCE_MS_THRESHOLD)
{
size_t dummy;
unsigned int start = sysTimer::GetSystemMsTime();
NTSTATUS queryStatus = NtDll::NtQueryVirtualMemory(process, fn.m_pMem, MemoryBasicInformation, &info, sizeof(info), &dummy);
if( queryStatus >= 0)
{
//gnetDebug3("0x%08X - NtQueryVirtualMemory Succeeded", RTMA_ID);
bFinished = false;
// Verify we are getting useful info from NtVirtualQueryMemoryFn
if(fn.m_pMem != info.BaseAddress)
{
gnetDebug3("0x%08X - Base Addresses don't match", RTMA_ID);
failed = true;
}
else
{
fn.m_pMem += info.RegionSize;
unsigned int delta = sysTimer::GetSystemMsTime()-start;
//gnetDebug3("0x%08X - Checking - %"I64FMT"X - %d", RTMA_ID,fn.m_pMem, delta);
fn.Check(info, failed);
numDelayed++;
aggregate+=delta;
}
}
else
{
// I imagine there would be at least 3 executable blocks if not a load more. Game in dev has over 100
gnetDebug3("0x%08X - NtQueryVirtualMemory Failed - 0x%08X ", RTMA_ID, queryStatus);
if(fn.m_numProcessed < 3)
failed = true;
break;
}
}
#elif RSG_ORBIS
SceKernelVirtualQueryInfo sceInfo;
bool bFinished = true;
unsigned int numDelayed = 0;
gnetDebug3("0x%08X - Enumerating Memory Regions", RTMA_ID);
// For every memory region in the user space
unsigned int aggregate = 0;
while(!failed && aggregate < RTMA_PERFORMANCE_MS_THRESHOLD)
{
unsigned int start = sysTimer::GetSystemMsTime();
int virtualQueryResult = sceKernelVirtualQuery(fn.m_pMem, SCE_KERNEL_VQ_FIND_NEXT, &sceInfo, sizeof(sceInfo));
if (virtualQueryResult == SCE_KERNEL_ERROR_EACCES)
{
// Ensuring that we set the finished to true, if we've gotten a kernel acces error
bFinished = true;
break;
}
// Okay, now according to Sony, here are the only cases to which memory is valid for
// querying
if ((sceInfo.isFlexibleMemory || sceInfo.isDirectMemory) && (sceInfo.protection & SCE_KERNEL_PROT_CPU_READ))
{
bFinished = false;
// Set the end pointer
fn.m_pMem = (u8*)sceInfo.end;
// Create our delta time
unsigned int delta = sysTimer::GetSystemMsTime()-start;
gnetDebug3("0x%08X - Checking - %p - %d", RTMA_ID,fn.m_pMem, delta);
// Perform the checj
fn.Check(sceInfo, failed);
// Aggregate
numDelayed++;
aggregate+=delta;
}
else
{
gnetDebug3("0x%08X - sceKernelVirtualQuery Failed - 0x%08X ", RTMA_ID, virtualQueryResult);
if(fn.m_numProcessed < 3)
failed = true;
break;
}
}
#endif
if(failed)
{
bFinished = true;
gnetDebug3("0x%08X - Memory check failed! This indicates that the executable has been tampered with.", RTMA_ID);
}
else
{
gnetDebug3("0x%08X - Memory check did NOT fail!", RTMA_ID);
}
// If moving onto next check, reset values
if(bFinished)
{
fn.Reset();
}
gnetDebug3("0x%08X - Finished Enumerating Memory Regions - %d", RTMA_ID, aggregate);
return bFinished;
}
#endif
// Purpose: Checks the integrity of supporting modules and libraries
// Return true if supporting modules are not compromised, false otherwise
// For Windows this is NtDll and Kernel32
__forceinline bool RtmaPlugin_VerifySupportModules()
{
#if RSG_PC
if(NetworkInterface::IsGameInProgress())
{
// Check if NtDll is flagged as compromised
if(NtDll::HasBeenHacked())
{
RageSecPluginGameReactionObject obj(REACT_GAMESERVER, RTMA_ID, 0x668FF11A, 0x84B0395B, 0x1FD96845);
rageSecGamePluginManager::GetRageSecGameReactionObjectQueue()->Push(obj);
return false;
}
if(Kernel32::HasBeenHacked())
{
RageSecPluginGameReactionObject obj(REACT_GAMESERVER, RTMA_ID, 0x668FF11A, 0x84B0395B, 0x6DDB9C21);
rageSecGamePluginManager::GetRageSecGameReactionObjectQueue()->Push(obj);
return false;
}
}
#endif
return true;
}
// Purpose: Check if Tunables is loaded properly
// Return true if Tunables is loaded, false otherwise
__forceinline bool RtmaPlugin_VerifyTunableLoad()
{
if(!Tunables::GetInstance().HaveTunablesLoaded())
{
gnetDebug3("0x%08X - Tunables have not been loaded", RTMA_ID);
return false;
}
return true;
}
// Purpose: Check the CRC Checksum of the MemoryCheck object to detect tamper
// Return true if MemoryCheck is not modified, false otherwise
__forceinline bool RtmaPlugin_VerifyMemoryCheck(MemoryCheck *memoryCheck)
{
#if RSG_PC
if( memoryCheck->GetXorCrc() !=
(memoryCheck->GetVersionAndType() ^
memoryCheck->GetAddressStart() ^
memoryCheck->GetSize() ^
// This is a dirty hack. Below, we set the processed flag with an OR operator
// which was causing this tamper metric to incorrectly send.
// And since that flag is well outside my range, I'm just going to && this
// accordingly, so that we still get the correct XOR check.
memoryCheck->GetFlags() ^
memoryCheck->GetValue()))
{
// What do I fill in here?
CNetworkTelemetry::AppendInfoMetric(0x73617303, 0x5598B7EA, 0xB70A4BCB);
gnetDebug3("0x%08X - Memory Check %d doesn't match the XOR'ing it would expect", RTMA_ID, sm_nextCRCIndex);
return false;
}
#else
(void)memoryCheck; //Suppressing compiler error
#endif //RSG_PC
return true;
}
// Purpose: Check if the game session is in multiplayer or not
// Return true if game is in multiplayer, false otherwise
__forceinline bool RtmaPlugin_VerifyMultiplayer()
{
bool isMultiplayerManual = CNetwork::IsNetworkSessionValid() &&
NetworkInterface::IsGameInProgress() &&
(CNetwork::GetNetworkSession().IsSessionActive()
|| CNetwork::GetNetworkSession().IsTransitionActive()
|| CNetwork::GetNetworkSession().IsTransitionMatchmaking()
|| CNetwork::GetNetworkSession().IsRunningDetailQuery());
return isMultiplayerManual;
}
// Purpose: Check if known malicious byte string is present in memory
// Return true if successfully completed, false otherwise
bool RtmaPlugin_PageBytestringHandler(MemoryCheck *memoryCheck, bool &failed)
{
bool retVal = true;
#if RSG_PC || RSG_ORBIS
u32 xorValue = memoryCheck->GetValue() ^ MemoryCheck::GetMagicXorValue();
u32 startAddr = memoryCheck->GetAddressStart() ^ xorValue;
u32 len = memoryCheck->GetSize() ^ xorValue;
gnetDebug3("0x%08X - Starting Page Byte String Check", RTMA_ID);
static MemoryRegionCheckPageByteString memoryPageStringCheck;
//@@: location RTMAPLUGIN_WORK_DETERMINE_CHECK_PAGE_BYTE_STRING_SETUP
memoryPageStringCheck.Setup(memoryCheck->GetValue(), startAddr, len, memoryCheck->GetFlags() ^ xorValue);
retVal = EnumerateMemoryRegions(memoryPageStringCheck, failed);
#else
(void)memoryCheck; //Suppressing compiler error
(void)failed; //Suppressing compiler error
#endif // RSG_PC | RSG_ORBIS
return retVal;
}
// Purpose: Queue RageSecPluginGameReactionObjects to central queue depending on MemoryCheck flag values
// Returns nothing
void RtmaPlugin_QueueReactions(RageSecPluginGameReactionObject* obj, u32 flags)
{
if (flags & MemoryCheck::FLAG_Telemetry)
{
gnetDebug3("0x%08X - Queuing Telem", RTMA_ID);
obj->type = REACT_TELEMETRY;
rageSecGamePluginManager::GetRageSecGameReactionObjectQueue()->Push(*obj);
}
if (flags & MemoryCheck::FLAG_Report)
{
gnetDebug3("0x%08X - Queuing P2P", RTMA_ID);
obj->type = REACT_P2P_REPORT;
rageSecGamePluginManager::GetRageSecGameReactionObjectQueue()->Push(*obj);
}
if (flags & MemoryCheck::FLAG_KickRandomly)
{
gnetDebug3("0x%08X - Queuing Random Kick", RTMA_ID);
obj->type = REACT_KICK_RANDOMLY;
rageSecGamePluginManager::GetRageSecGameReactionObjectQueue()->Push(*obj);
}
if (flags & MemoryCheck::FLAG_NoMatchmake)
{
gnetDebug3("0x%08X - Queuing MatchMake Bust", RTMA_ID);
obj->type = REACT_DESTROY_MATCHMAKING;
rageSecGamePluginManager::GetRageSecGameReactionObjectQueue()->Push(*obj);
}
if (flags & MemoryCheck::FLAG_Crash)
{
gnetDebug3("0x%08X - Invoking pgStreamer Crash", RTMA_ID);
RageSecInduceStreamerCrash();
}
if (flags & MemoryCheck::FLAG_Gameserver)
{
gnetDebug3("0x%08X - Queuing Gameserver Report", RTMA_ID);
obj->type = REACT_GAMESERVER;
rageSecGamePluginManager::GetRageSecGameReactionObjectQueue()->Push(*obj);
}
return;
}
bool RtmaPlugin_Create()
{
gnetDebug3("0x%08X - Created", RTMA_ID);
//@@: location RTMAPLUGIN_CREATE
sm_trashValue += sysTimer::GetSystemMsTime();
return true;
}
bool RtmaPlugin_Configure()
{
gnetDebug3("0x%08X - Configure", RTMA_ID);
//@@: location RTMAPLUGIN_CONFIGURE
sm_trashValue -= sysTimer::GetSystemMsTime();
return true;
}
bool RtmaPlugin_Work()
{
gnetDebug3("0x%08X - Work Entry", RTMA_ID);
//@@: range RTMAPLUGIN_WORK_SHORT_CIRCUIT {
if(!CGame::IsSessionInitialized()) { return true; }
if(!RtmaPlugin_VerifySupportModules()) { return true; } // Check if dependency modules are compromised
if(!RtmaPlugin_VerifyTunableLoad()) { return true; } // Check if Tunables is loaded
#if RSG_PC
sm_maxRegionSizeToCheck = Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("MAX_REGION_SIZE_TO_CHECK", 0x7cb5e69d), 0x10000);
#endif //RSG_PC
int numChecks = Tunables::GetInstance().GetNumMemoryChecks();
if(numChecks == 0)
{
// Pass iteration until Tunables has memory checks available
gnetDebug3("0x%08X - No MemoryChecks loaded by tunables.", RTMA_ID);
return true;
}
MemoryCheck memoryCheck;
Tunables::GetInstance().GetMemoryCheck(sm_nextCRCIndex, memoryCheck);
gnetDebug3("0x%08X - Fetching Memory Check %d", RTMA_ID, sm_nextCRCIndex);
if(!RtmaPlugin_VerifyMemoryCheck(&memoryCheck)) { return true; } // Verify MemoryCheck CRC
u32 xorValue = memoryCheck.GetValue() ^ MemoryCheck::GetMagicXorValue();
gnetDebug3("0x%08X - Number Other Players:%d", RTMA_ID, netInterface::GetNumPhysicalPlayers());
bool bMoveOntoNextCheck = true;
bool isMultiplayer = RtmaPlugin_VerifyMultiplayer();
//@@: } RTMAPLUGIN_WORK_SHORT_CIRCUIT
//@@: location RTMAPLUGIN_WORK_CHECK_ENTER
sm_trashValue /= sysTimer::GetSystemMsTime();
if(isMultiplayer && !((memoryCheck.GetFlags() ^ xorValue) & MemoryCheck::FLAG_MP_Processed))
{
bool failed = false;
//@@: range RTMAPLUGIN_WORK_DETERMINE_CHECK {
MemoryCheck::CheckType currType = memoryCheck.getCheckType();
if(currType == MemoryCheck::CHECK_PageByteString)
{
bMoveOntoNextCheck = RtmaPlugin_PageBytestringHandler(&memoryCheck, failed);
}
//@@: } RTMAPLUGIN_WORK_DETERMINE_CHECK
//@@: location RTMAPLUGIN_WORK_CHECK_FAILED
if(failed)
{
// Check failed, add to list
gnetDebug3("0x%08X - Code CRC failed! Queuing Reaction Object", RTMA_ID);
u32 actualVal = memoryCheck.GetValue();
SYS_CS_SYNC(m_cs);
//@@: location RTMAPLUGIN_WORK_ADD_TO_FAILED_CRCS
RageSecPluginGameReactionObject obj(
REACT_INVALID,
RTMA_ID,
memoryCheck.GetVersionAndType() ^ xorValue,
memoryCheck.GetAddressStart() ^ xorValue,
actualVal);
u32 flags = memoryCheck.GetFlags() ^ xorValue;
//@@: range RTMAPLUGIN_WORK_QUEUE_OBJECT {
RtmaPlugin_QueueReactions(&obj, flags);
// Flag this memory check as processed
//@@: location RTMAPLUGIN_WORK_SET_PROCESSED
flags |= MemoryCheck::FLAG_MP_Processed;
//@@: } RTMAPLUGIN_WORK_QUEUE_OBJECT
// Calling the instance so it sets the correct memoryCheck, instead of our locally accessed
// one we're just using to read information. The better solution is to change
// that into a pointer, so I can read/write to it.
gnetDebug3("0x%08X - Setting memory index %d to processed", RTMA_ID, sm_nextCRCIndex);
//@@: location RTMAPLUGIN_WORK_SET_MEMORY_FLAGS
Tunables::GetInstance().SetMemoryCheckFlags(sm_nextCRCIndex, flags^xorValue);
}
else
{
gnetDebug3("0x%08X - CRC Check %d did not fail.", RTMA_ID, sm_nextCRCIndex);
}
}
//@@: location RTMAPLUGIN_WORK_MOVEONTONEXTCHECK
if(bMoveOntoNextCheck)
{
// Move to next function
//@@: range RTMAPLUGIN_WORK_MOVEONTONEXTCHECK_BODY {
gnetDebug3("0x%08X - Moving onto index %d of %d",RTMA_ID, sm_nextCRCIndex, numChecks-1);
++sm_nextCRCIndex;
if(sm_nextCRCIndex >= numChecks)
{
sm_nextCRCIndex = 0;
}
//@@: } RTMAPLUGIN_WORK_MOVEONTONEXTCHECK_BODY
}
return true;
}
bool RtmaPlugin_Init()
{
// Since there's no real initialization here necessary, we can just assign our
// creation function, our destroy function, and just register the object
// accordingly
RageSecPluginParameters rs;
RageSecPluginExtendedInformation ei;
ei.startTime = sysTimer::GetSystemMsTime();
ei.frequencyInMs = RTMA_POLLING_MS;
rs.version.major = PLUGIN_VERSION_MAJOR;
rs.version.minor = PLUGIN_VERSION_MINOR;
rs.type = EXECUTE_PERIODIC;
rs.extendedInfo = ei;
rs.Create = &RtmaPlugin_Create;
rs.Configure = &RtmaPlugin_Configure;
rs.Destroy = NULL;
rs.DoWork = &RtmaPlugin_Work;
rs.OnSuccess = NULL;
rs.OnCancel = NULL;
rs.OnFailure = NULL;
// Register the function
// TO CREATE A NEW ID, GO TO WWW.RANDOM.ORG/BYTES AND GENERATE
// FOUR BYTES TO REPRESENT THE ID.
rageSecPluginManager::RegisterPluginFunction(&rs, RTMA_ID);
return true;
}
#endif // RAGE_SEC_TASK_RTMA
#endif // RAGE_SEC