WELCOME TO INFOCHEATS.NET

INFOCHEATS is a community-driven platform focused on free game cheats, cheat development, and verified commercial software for a wide range of popular games. We provide a large collection of free cheats shared by the community. All public releases are checked for malicious code to reduce the risk of viruses, malware, or unwanted software before users interact with them.

Alongside free content, INFOCHEATS hosts an active marketplace with many independent sellers offering commercial cheats. Each product is discussed openly, with user feedback, reviews, and real usage experience available to help you make informed decisions before purchasing.

Whether you are looking for free cheats, exploring paid solutions, comparing sellers, or studying how cheats are developed and tested, INFOCHEATS brings everything together in one place — transparently and community-driven.

Question Valorant - KMODE_EXCEPTION_NOT_HANDLED BSOD in Custom Kernel Driver Injection

byte_corvus

Newbie
Newbie

byte_corvus

Newbie
Newbie
Status
Offline
Joined
Mar 3, 2026
Messages
30
Reaction score
7
Has anyone tested this yet?

Looks like a typical "first-timer" kernel driver setup, but keep in mind that trying to write a driver for Val is basically begging for a permanent HWID ban on your main rig.

If you are getting a BSOD with 0x1E, it usually means your kernel-mode code is trying to access invalid memory or you've messed up the stack/paging. You're using ManualMap in kernel mode without proper exception handling or page checks, which is a massive red flag. Also, you're hardcoding `ZwAllocateVirtualMemory` inside the target process context without ensuring the APC/process context is correct. If you're doing this from a system thread, you're gonna have a bad time.

A few things to check:

  1. Context Switching: Your `ManualMapInject` relies on `PsLookupProcessByProcessId` but you aren't properly attaching to the target process before calling `ZwAllocateVirtualMemory`. You need to use `KeStackAttachProcess` so the kernel actually knows where to allocate that memory. That's a classic cause for the crash.
  2. Write Protection: You've got `DisableWriteProtection` functions defined but I don't see them being called inside your R/W logic. Writing to read-only memory will definitely BSOD you instantly.
  3. Validation: You aren't validating the buffer source enough. Anyone can pass a garbage address and nuke the system.
Code:
/*
    Made by ryxu-xo on GitHub
    Main.cpp - Entry point and core logic for Ryxu Kernel Mode Driver
*/
 
#include <ntifs.h>
#include "Definitions.h"
#include "Utils.h"
#include "Comm.h"
#include "KernelCompat.h"
 
 
#define DLL_MAX_SIZE (20 * 1024 * 1024) // 20MB
 
// Shared memory section handle and pointer
static HANDLE g_SectionHandle = NULL;
static PRYXU_COMM_BLOCK g_CommBlock = nullptr;
 
// --- Helper: Case-insensitive unicode string compare ---
BOOLEAN RyxuStrICmp(const WCHAR* s1, const WCHAR* s2) {
    while (*s1 && *s2) {
        WCHAR c1 = (*s1 >= L'a' && *s1 <= L'z') ? *s1 - 32 : *s1;
        WCHAR c2 = (*s2 >= L'a' && *s2 <= L'z') ? *s2 - 32 : *s2;
        if (c1 != c2) return FALSE;
        ++s1; ++s2;
    }
    return *s1 == *s2;
}
 
// --- Pattern scan utility (simplified) ---
PVOID PatternScan(PVOID base, SIZE_T size, const char* pattern, const char* mask) {
    for (SIZE_T i = 0; i < size - strlen(mask); ++i) {
        BOOLEAN found = TRUE;
        for (SIZE_T j = 0; mask[j]; ++j) {
            if (mask[j] != '?' && ((PUCHAR)base)[i + j] != (UCHAR)pattern[j]) {
                found = FALSE; break;
            }
        }
        if (found) return (PUCHAR)base + i;
    }
    return nullptr;
}
 
// --- Stealth: Clear traces ---
NTSTATUS ClearMmUnloadedDrivers() {
    // This is a simplified stub. Real implementation requires kernel pattern scan and memory patching.
    return STATUS_SUCCESS;
}
 
// --- Minimal HandleRequest implementation ---
NTSTATUS GetProcessModuleBase(ULONG pid, const WCHAR* moduleName, PVOID* base);
NTSTATUS ReadVirtualMemory(ULONG pid, PVOID src, PVOID dst, SIZE_T size);
NTSTATUS WriteVirtualMemory(ULONG pid, PVOID dst, PVOID src, SIZE_T size);
NTSTATUS ManualMapInject(ULONG pid, PVOID dllBuffer, SIZE_T dllSize);
extern "C" NTSTATUS HandleRequest(PRYXU_REQUEST req) {
    if (!req) {
        DbgPrint("[ryxu] HandleRequest: req is NULL\n");
        return STATUS_INVALID_PARAMETER;
    }
    DbgPrint("[ryxu] HandleRequest: Command=0x%X, PID=%lu, Buffer=%p, Size=%llu\n", req->Command, req->ProcessId, req->Buffer, req->Size);
    switch (req->Command) {
        case RYXU_CMD::GetProcessModuleBase: {
            DbgPrint("[ryxu] HandleRequest: GetProcessModuleBase\n");
            PVOID base = nullptr;
            NTSTATUS st = GetProcessModuleBase(req->ProcessId, req->ModuleName, &base);
            if (NT_SUCCESS(st)) {
                *(PVOID*)req->Buffer = base;
            }
            return st;
        }
        case RYXU_CMD::ReadVirtualMemory:
            DbgPrint("[ryxu] HandleRequest: ReadVirtualMemory\n");
            return ReadVirtualMemory(req->ProcessId, req->Address, req->Buffer, req->Size);
        case RYXU_CMD::WriteVirtualMemory:
            DbgPrint("[ryxu] HandleRequest: WriteVirtualMemory\n");
            return WriteVirtualMemory(req->ProcessId, req->Address, req->Buffer, req->Size);
        case RYXU_CMD::ManualMapInject:
            DbgPrint("[ryxu] HandleRequest: ManualMapInject\n");
            return ManualMapInject(req->ProcessId, req->Buffer, req->Size);
        default:
            DbgPrint("[ryxu] HandleRequest: Unknown command 0x%X\n", req->Command);
            return STATUS_INVALID_DEVICE_REQUEST;
    }
}
 
NTSTATUS ClearPiDDBCacheTable() {
    // This is a simplified stub. Real implementation requires kernel pattern scan and memory patching.
    return STATUS_SUCCESS;
}
 
 
 
// --- Shared Memory Communication (named section) ---
NTSTATUS SetupSharedMemory() {
    DbgPrint("[ryxu] SetupSharedMemory called\n");
 
    UNICODE_STRING sectionName;
    RtlInitUnicodeString(&sectionName, RYXU_SECTION_NAME);
 
    // Set up a security descriptor with a NULL DACL (full access)
    SECURITY_DESCRIPTOR sd;
    NTSTATUS status = RtlCreateSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
    if (!NT_SUCCESS(status)) {
        DbgPrint("[ryxu] RtlCreateSecurityDescriptor failed: 0x%X\n", status);
        return status;
    }
    
    status = RtlSetDaclSecurityDescriptor(&sd, TRUE, NULL, FALSE);
    if (!NT_SUCCESS(status)) {
        DbgPrint("[ryxu] RtlSetDaclSecurityDescriptor failed: 0x%X\n", status);
        return status;
    }
 
    OBJECT_ATTRIBUTES oa;
    InitializeObjectAttributes(&oa, &sectionName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, &sd);
 
    LARGE_INTEGER maxSize;
    maxSize.QuadPart = sizeof(RYXU_COMM_BLOCK);
 
    status = ZwCreateSection(
        &g_SectionHandle,
        SECTION_ALL_ACCESS,
        &oa,
        &maxSize,
        PAGE_READWRITE,
        SEC_COMMIT,
        NULL
    );
    if (!NT_SUCCESS(status)) {
        DbgPrint("[ryxu] ZwCreateSection failed: 0x%X\n", status);
        if (status == STATUS_OBJECT_NAME_COLLISION) {
            DbgPrint("[ryxu] Section already exists, trying to open it\n");
            // Try to open existing section instead
            status = ZwOpenSection(&g_SectionHandle, SECTION_ALL_ACCESS, &oa);
            if (!NT_SUCCESS(status)) {
                DbgPrint("[ryxu] ZwOpenSection failed: 0x%X\n", status);
                return status;
            }
        } else {
            return status;
        }
    }
 
    // Map the section into kernel address space
    // Note: In a system thread, ZwCurrentProcess() returns the System process
    // but the mapping will work because we're in kernel mode
    SIZE_T viewSize = sizeof(RYXU_COMM_BLOCK);
    PVOID baseAddress = NULL;
    
    status = ZwMapViewOfSection(
        g_SectionHandle,
        ZwCurrentProcess(),
        &baseAddress,
        0,
        sizeof(RYXU_COMM_BLOCK),
        NULL,
        &viewSize,
        ViewUnmap,
        0,
        PAGE_READWRITE
    );
    if (!NT_SUCCESS(status)) {
        DbgPrint("[ryxu] ZwMapViewOfSection failed: 0x%X\n", status);
        ZwClose(g_SectionHandle);
        g_SectionHandle = NULL;
        return status;
    }
    
    g_CommBlock = (PRYXU_COMM_BLOCK)baseAddress;
    if (!g_CommBlock) {
        DbgPrint("[ryxu] g_CommBlock is NULL after mapping!\n");
        ZwUnmapViewOfSection(ZwCurrentProcess(), baseAddress);
        ZwClose(g_SectionHandle);
        g_SectionHandle = NULL;
        return STATUS_UNSUCCESSFUL;
    }
    
    RtlZeroMemory(g_CommBlock, sizeof(RYXU_COMM_BLOCK));
    g_CommBlock->Magic = RYXU_COMM_MAGIC;
    DbgPrint("[ryxu] Shared memory section created and initialized at %p\n", g_CommBlock);
    return STATUS_SUCCESS;
}
 
// --- GetProcessModuleBase implementation ---
NTSTATUS GetProcessModuleBase(ULONG pid, const WCHAR* moduleName, PVOID* base) {
    PEPROCESS process = NULL;
    NTSTATUS status = PsLookupProcessByProcessId((HANDLE)pid, &process);
    if (!NT_SUCCESS(status) || !process)
        return STATUS_INVALID_PARAMETER;
    
    NTSTATUS ret = STATUS_NOT_FOUND;
    __try {
        // Use our safe PEB getter that handles different Windows versions
        PPEB peb = GetProcessPeb(process);
        if (!peb || !peb->Ldr) {
            DbgPrint("[ryxu] GetProcessModuleBase: Invalid PEB or Ldr\n");
            ret = STATUS_UNSUCCESSFUL;
            __leave;
        }
        
        // Walk the InLoadOrderModuleList
        for (PLIST_ENTRY list = peb->Ldr->InLoadOrderModuleList.Flink;
             list != &peb->Ldr->InLoadOrderModuleList;
             list = list->Flink) {
            PLDR_DATA_TABLE_ENTRY entry = CONTAINING_RECORD(list, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
            if (entry->BaseDllName.Buffer && entry->BaseDllName.Length > 0) {
                // Check if module name matches
                if (RyxuStrICmp(entry->BaseDllName.Buffer, moduleName)) {
                    *base = entry->DllBase;
                    DbgPrint("[ryxu] GetProcessModuleBase: Found %wZ at %p\n", &entry->BaseDllName, entry->DllBase);
                    ret = STATUS_SUCCESS;
                    __leave;
                }
            }
        }
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        DbgPrint("[ryxu] GetProcessModuleBase: Exception 0x%X\n", GetExceptionCode());
        ret = STATUS_ACCESS_VIOLATION;
    }
    ObDereferenceObject(process);
    return ret;
}
 
// --- Read/Write Virtual Memory ---
NTSTATUS ReadVirtualMemory(ULONG pid, PVOID src, PVOID dst, SIZE_T size) {
    DbgPrint("[ryxu] ReadVirtualMemory: pid=%lu, src=%p, dst=%p, size=%llu\n", pid, src, dst, size);
    PEPROCESS process = NULL;
    NTSTATUS status = PsLookupProcessByProcessId((HANDLE)pid, &process);
    if (!NT_SUCCESS(status) || !process) {
        DbgPrint("[ryxu] ReadVirtualMemory: PsLookupProcessByProcessId failed\n");
        return STATUS_INVALID_PARAMETER;
    }
    NTSTATUS ret = STATUS_UNSUCCESSFUL;
    __try {
        if (!src || !dst || size == 0 || size > DLL_MAX_SIZE) {
            DbgPrint("[ryxu] ReadVirtualMemory: Invalid pointer or size\n");
            ret = STATUS_INVALID_PARAMETER;
            __leave;
        }
        SIZE_T bytes = 0;
        status = MmCopyVirtualMemory(process, src, PsGetCurrentProcess(), dst, size, KernelMode, &bytes);
        DbgPrint("[ryxu] ReadVirtualMemory: MmCopyVirtualMemory status=0x%X, bytes=%llu\n", status, bytes);
        ret = status;
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        DbgPrint("[ryxu] ReadVirtualMemory: Exception occurred!\n");
        ret = STATUS_ACCESS_VIOLATION;
    }
    ObDereferenceObject(process);
    return ret;
}
 
NTSTATUS WriteVirtualMemory(ULONG pid, PVOID dst, PVOID src, SIZE_T size) {
    DbgPrint("[ryxu] WriteVirtualMemory: pid=%lu, dst=%p, src=%p, size=%llu\n", pid, dst, src, size);
    PEPROCESS process = NULL;
    NTSTATUS status = PsLookupProcessByProcessId((HANDLE)pid, &process);
    if (!NT_SUCCESS(status) || !process) {
        DbgPrint("[ryxu] WriteVirtualMemory: PsLookupProcessByProcessId failed\n");
        return STATUS_INVALID_PARAMETER;
    }
    NTSTATUS ret = STATUS_UNSUCCESSFUL;
    __try {
        if (!src || !dst || size == 0 || size > DLL_MAX_SIZE) {
            DbgPrint("[ryxu] WriteVirtualMemory: Invalid pointer or size\n");
            ret = STATUS_INVALID_PARAMETER;
            __leave;
        }
        SIZE_T bytes = 0;
        status = MmCopyVirtualMemory(PsGetCurrentProcess(), src, process, dst, size, KernelMode, &bytes);
        DbgPrint("[ryxu] WriteVirtualMemory: MmCopyVirtualMemory status=0x%X, bytes=%llu\n", status, bytes);
        ret = status;
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        DbgPrint("[ryxu] WriteVirtualMemory: Exception occurred!\n");
        ret = STATUS_ACCESS_VIOLATION;
    }
    ObDereferenceObject(process);
    return ret;
}
 
 
// --- ManualMapInject (full PE loader) ---
NTSTATUS ManualMapInject(ULONG pid, PVOID dllBuffer, SIZE_T dllSize) {
    DbgPrint("[ryxu] ManualMapInject: BEGIN pid=%lu, dllBuffer=%p, dllSize=%llu\n", pid, dllBuffer, dllSize);
    if (!dllBuffer || dllSize == 0 || dllSize > DLL_MAX_SIZE) {
        DbgPrint("[ryxu] ManualMapInject: Invalid DLL buffer or size (%p, %llu)\n", dllBuffer, dllSize);
        return STATUS_INVALID_BUFFER_SIZE;
    }
    
    PEPROCESS targetProcess = NULL;
    NTSTATUS status = PsLookupProcessByProcessId((HANDLE)pid, &targetProcess);
    if (!NT_SUCCESS(status) || !targetProcess) {
        DbgPrint("[ryxu] ManualMapInject: PsLookupProcessByProcessId failed: 0x%X\n", status);
        return STATUS_INVALID_PARAMETER;
    }
    
    NTSTATUS ret = STATUS_UNSUCCESSFUL;
    HANDLE targetProcessHandle = NULL;
    
    __try {
        DbgPrint("[ryxu] ManualMapInject: Parsing PE headers\n");
        if (dllSize < sizeof(IMAGE_DOS_HEADER)) {
            DbgPrint("[ryxu] ManualMapInject: DLL too small for DOS header\n");
            ret = STATUS_INVALID_IMAGE_FORMAT;
            __leave;
        }
        PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)dllBuffer;
        if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
            DbgPrint("[ryxu] ManualMapInject: Invalid DOS signature\n");
            ret = STATUS_INVALID_IMAGE_FORMAT;
            __leave;
        }
        if ((SIZE_T)dos->e_lfanew > dllSize - sizeof(IMAGE_NT_HEADERS64)) {
            DbgPrint("[ryxu] ManualMapInject: e_lfanew out of bounds\n");
            ret = STATUS_INVALID_IMAGE_FORMAT;
            __leave;
        }
        PIMAGE_NT_HEADERS64 nt = (PIMAGE_NT_HEADERS64)((PUCHAR)dllBuffer + dos->e_lfanew);
        if (nt->Signature != IMAGE_NT_SIGNATURE) {
            DbgPrint("[ryxu] ManualMapInject: Invalid NT signature\n");
            ret = STATUS_INVALID_IMAGE_FORMAT;
            __leave;
        }
        SIZE_T imageSize = nt->OptionalHeader.SizeOfImage;
        if (imageSize == 0 || imageSize > DLL_MAX_SIZE) {
            DbgPrint("[ryxu] ManualMapInject: Invalid image size\n");
            ret = STATUS_INVALID_IMAGE_FORMAT;
            __leave;
        }
        
        // Get handle to target process for memory allocation
        status = ObOpenObjectByPointer(
            targetProcess,
            OBJ_KERNEL_HANDLE,
            NULL,
            PROCESS_ALL_ACCESS,
            *PsProcessType,
            KernelMode,
            &targetProcessHandle
        );
        if (!NT_SUCCESS(status)) {
            DbgPrint("[ryxu] ManualMapInject: ObOpenObjectByPointer failed: 0x%X\n", status);
            ret = status;
            __leave;
        }
        
        DbgPrint("[ryxu] ManualMapInject: ZwAllocateVirtualMemory in target process (size=%llu)\n", imageSize);
        PVOID remoteBase = NULL;
        SIZE_T allocSize = imageSize;
        status = ZwAllocateVirtualMemory(
            targetProcessHandle,
            &remoteBase,
            0,
            &allocSize,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_EXECUTE_READWRITE
        );
        if (!NT_SUCCESS(status) || !remoteBase) {
            DbgPrint("[ryxu] ManualMapInject: ZwAllocateVirtualMemory failed: 0x%X\n", status);
            ret = status;
            __leave;
        }
        DbgPrint("[ryxu] ManualMapInject: PE headers parsed and memory allocated. (remoteBase=%p)\n", remoteBase);
        ret = STATUS_SUCCESS;
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        DbgPrint("[ryxu] ManualMapInject: Exception 0x%X!\n", GetExceptionCode());
        ret = STATUS_ACCESS_VIOLATION;
    }
    
    if (targetProcessHandle) {
        ZwClose(targetProcessHandle);
    }
    ObDereferenceObject(targetProcess);
    return ret;
}
 
// --- CR0/CR3 Manipulation (if needed for memory protection) ---
extern "C" void __writecr0(unsigned __int64);
extern "C" unsigned __int64 __readcr0(void);
 
void DisableWriteProtection() {
    unsigned __int64 cr0 = __readcr0();
    cr0 &= ~(1ULL << 16);
    __writecr0(cr0);
}
 
void EnableWriteProtection() {
    unsigned __int64 cr0 = __readcr0();
    cr0 |= (1ULL << 16);
    __writecr0(cr0);
}
 
// --- System Thread for Request Handling ---
extern "C" VOID RequestThread(PVOID Context) {
    UNREFERENCED_PARAMETER(Context);
    DbgPrint("[ryxu] RequestThread started\n");
    while (TRUE) {
        __try {
            if (g_CommBlock && g_CommBlock->RequestPending == 1) {
                DbgPrint("[ryxu] Got request: cmd=0x%X pid=%lu\n", g_CommBlock->Request.Command, g_CommBlock->Request.ProcessId);
                g_CommBlock->ResultStatus = HandleRequest(&g_CommBlock->Request);
                g_CommBlock->ResultReady = 1;
                g_CommBlock->RequestPending = 0;
                DbgPrint("[ryxu] Request handled, status=0x%X\n", g_CommBlock->ResultStatus);
            }
        } __except(EXCEPTION_EXECUTE_HANDLER) {
            DbgPrint("[ryxu] Exception in RequestThread loop!\n");
        }
        LARGE_INTEGER delay;
        delay.QuadPart = -10000; // 1ms
        KeDelayExecutionThread(KernelMode, FALSE, &delay);
    }
}
 
// --- Entry Point ---
extern "C"
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    UNREFERENCED_PARAMETER(DriverObject);
    UNREFERENCED_PARAMETER(RegistryPath);
 
    // Stealth: Clear traces
    ClearMmUnloadedDrivers();
    ClearPiDDBCacheTable();
 
    // OS version check
    ULONG major, minor, build;
    PsGetVersion(&major, &minor, &build, nullptr);
 
    DbgPrint("[ryxu] DriverEntry: Starting\n");
    // Setup named section for shared memory communication
    NTSTATUS commStatus = SetupSharedMemory();
    if (!NT_SUCCESS(commStatus) || !g_CommBlock) {
        DbgPrint("[ryxu] SetupSharedMemory failed or g_CommBlock is NULL: 0x%X\n", commStatus);
        return commStatus;
    }
    DbgPrint("[ryxu] DriverEntry: Shared memory setup OK, g_CommBlock=%p\n", g_CommBlock);
    HANDLE hThread = NULL;
    NTSTATUS threadStatus = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, RequestThread, NULL);
    if (!NT_SUCCESS(threadStatus)) {
        DbgPrint("[ryxu] PsCreateSystemThread failed: 0x%X\n", threadStatus);
        return threadStatus;
    }
    if (hThread) ZwClose(hThread);
    DbgPrint("[ryxu] DriverEntry: Returning STATUS_SUCCESS\n");
    return STATUS_SUCCESS;
}

Code:
/*
    Made by ryxu-xo on GitHub
    Definitions.h - Core definitions and compile-time string encryption for Ryxu Kernel Mode Driver
*/
 
 
#pragma once
#include <ntifs.h>
 
 
// Compile-time XOR string encryption
template <size_t N, char KEY>
class XorStr {
    char encrypted[N];
public:
    constexpr XorStr(const char(&str)[N]) : encrypted{} {
        for (size_t i = 0; i < N; ++i)
            encrypted[i] = str[i] ^ KEY;
    }
    __forceinline const char* decrypt() {
        for (size_t i = 0; i < N; ++i)
            encrypted[i] ^= KEY;
        return encrypted;
    }
};
 
// IOCTL-like command codes (not real IOCTLs, just for internal protocol)
enum class RYXU_CMD : ULONG {
    GetProcessModuleBase = 0x1001,
    ReadVirtualMemory    = 0x1002,
    WriteVirtualMemory   = 0x1003,
    ManualMapInject      = 0x1004
};
 
typedef struct _RYXU_REQUEST {
    RYXU_CMD Command;
    ULONG    ProcessId;
    PVOID    Address;
    PVOID    Buffer;
    SIZE_T   Size;
    WCHAR    ModuleName[260];
    // For ManualMapInject, Buffer points to DLL buffer, Size is DLL size
} RYXU_REQUEST, *PRYXU_REQUEST;
 
// Utility macros for pool allocation
#if (NTDDI_VERSION >= NTDDI_WIN10_RS5)
#define RYXU_ALLOC(size) ExAllocatePool2(POOL_FLAG_NON_PAGED, size, 'RYXU')
#else
#define RYXU_ALLOC(size) ExAllocatePoolWithTag(NonPagedPool, size, 'RYXU')
#endif
 
#define RYXU_FREE(ptr) if(ptr) ExFreePool(ptr)
Also, if you're targeting Valorant, do NOT run this on your main PC. Vanguard will eat this driver alive in seconds. If you're really serious about learning this, look into how to communicate securely between user-mode and kernel-mode without using named sections, as that's easily detectable.
 
Last edited by a moderator:
Top