Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate

A program that defeats AutoHotKey???


  • This topic is locked This topic is locked
133 replies to this topic
Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
That is very unexpected. I thought SendInput() was identical to keybd_event() in every practical sense, except for the fact that SendInput is uninterruptible (and thus more desirable). In fact, I'm planning to add the following item to the to-do list:

Make hotstring auto-replace uninterruptible via SendInput(). This should prevent the user's own keystrokes from getting interspersed within the replacement text, which tends to happen when the CPU is under heavy load, or in certain apps that process keystrokes more slowly than average.

If you have time, perhaps you can double-check your findings about SendInput(). It would be very surprising to me if it behaved differently in games except to the extent that it might send the keystrokes too quickly (i.e. with no key-delay at all). Thanks.

Astaelan
  • Members
  • 34 posts
  • Last active: Dec 13 2007 03:39 AM
  • Joined: 23 Sep 2004
I think the difference in how this user accomplished their task is that they wrote a program attempting to use keybd_event to inject input themselves. Of course, it's possible this may fail under a number of conditions. 1) If the program uses a hook for input, this will never occur. 2) If the program checkes for injected input, as I thought mine did, this could also be ignored.

If the standard autohotkey works for you but mouse_event and keybd_event do not in your own application, I would have to call it user error, because I've done some extensive testing over the last month or so into SendInput and keybd_event and mouse_event calls. The only difference is that SendInput uses keybd_event and mouse_event to generate the events internally, allowing for a single point of contact for input injecting, via SendInput. SendInput superceeds keybd_event and mouse_event, that means the functionality hasn't fundamentally changed. The layer at which the injection occurs is still the same. If your game in question uses DirectInput, it may be that keybd_events don't work because you are not giving enough time on the DOWN events for the program to process it. By default I believe AutoHotKey implements a 10-50MS delay between DOWN and UP events. I was told increasing this delay could potentially fix my own input problems, but it didn't.

That said, Chris, here's all the information I can provide at this time. Sorry it isn't much.

First, google for kbfiltr and moufiltr, these 2 are going to be where we can solve our problem for low level injections.
Here's my strategy. First, create the mouse filter SYS file, incorporate it into the system, and somehow test that the driver is in fact functioning correctly (perhaps a messagebox on CAPSLOCK to test is sufficient). After this I get a bit hazy on how device drivers work. Being that they are .SYS and not .DLL, I'm not sure if standard library rules apply. Can I setup a function to export, which can then be called from other applications? Like if I wanted to export a function "InjectInput", then have an EXE I write include that library so it can call InjectInput? Seems to me this should be doable, but I've only ever done it with standard DLL's not system device drivers.
Next, check out the Windows 2003 DDK. There is some sample code around input that is a bit confusing at first glance, and again doesn't really help with showing how to add input into the stream, the only example I've found is transforming existing keystrokes.
The cap2ctrl does not use kbfiltr or kbclass device drivers to do it's work, in fact, I'm not entirely sure what it's doing, and if it's possible to inject input the way it works.

Anyways, I'm taking a bit of a break from this whole scene until there is someone else willing to work on writing some device drivers with me, or at least be able to offer a steady brain to bounce my ideas off, and suggest twists on my ideas that might work. Experience with device drivers would be a plus, but not required if you have a good knowledge of C/C++ otherwise.

Thanks, good luck, hope we can get this resolved in a few weeks. Keep me uptodate on your progress with the other AutoHotKey features, would be nice to know when you're getting close to finishing the planned features.

Talk to you later.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004

I've done some extensive testing over the last month or so into SendInput and keybd_event and mouse_event calls. The only difference is that SendInput uses keybd_event and mouse_event to generate the events internally, allowing for a single point of contact for input injecting, via SendInput.

Thanks for sharing that.

If your game in question uses DirectInput, it may be that keybd_events don't work because you are not giving enough time on the DOWN events for the program to process it.

I think Savage was saying that SendInput() didn't work, but keybd_event() did.

By default I believe AutoHotKey implements a 10-50MS delay between DOWN and UP events. I was told increasing this delay could potentially fix my own input problems, but it didn't.

That's true only for mouse clicks. Keystrokes have no delay between down and up. Only very rarely does this cause problems, such as with games that poll the keyboard at timed intervals. I doubt any modern commercial games do that anymore.

That said, Chris, here's all the information I can provide at this time. Sorry it isn't much.

Thanks for the info. I'll try to research those leads sometime in the next few weeks.

savage
  • Members
  • 207 posts
  • Last active: Jul 03 2008 03:12 AM
  • Joined: 02 Jul 2004
I don't think that Diablo 2 uses anything special for input. I wasn't using the hook, so I think that was why just using sendinput didn't work in jDoom. I've found that ahk can run jdoom fine. It might have just been a matter of how I sent the keys though. I think I was sending the keys as a repeated event, up down up down, whereas with ahk I was sending down and then up when I was done. I don't know, when I wrote my sad little vb app I didn't really know the details of what I was doing. I can give you my sendinput code if you like.

Astaelan
  • Members
  • 34 posts
  • Last active: Dec 13 2007 03:39 AM
  • Joined: 23 Sep 2004
As far as Diablo 2 goes, someone mentioned they had to write something a while back to get D2 to work, was that you?
If not, they mentioned they had written something using some other Win32API hooks, other than SendInput/keybd_event/mouse_event.
I don't know, but the game I am working on in specific has thwarted every effort so far and my wrist is swelling, I'm serious :p
The game in question is Priston Tale, and it's gameplay is similar to Diablo 2, but the input is attained in an odd manner that I can't figure out. So to defeat it, I want to try and write a .SYS device driver called an upper level filter, using kbfiltr and moufiltr, which according to all documentation I've found, CAN add data to the stream. I just haven't figured out how yet.

I might get some time to do a little testing here today, we'll see. I'm not real comfortable with writing device drivers, can really screw my system up trying :)

Astaelan
  • Members
  • 34 posts
  • Last active: Dec 13 2007 03:39 AM
  • Joined: 23 Sep 2004
Okay, well, I'm not sure this is entirely legal, but I'm not posting a complete code solution, and it comes with the DDK as example source to work with, but here goes.

I've been looking over the moufiltr example in the DDK, and I've found a couple pieces of code that are of particular interest. I will paste the whole file here, and then repaste the interesting sections.

/*--         
Copyright (c) 1998, 1999  Microsoft Corporation

Module Name:

    moufiltr.c

Abstract:

Environment:

    Kernel mode only.

Notes:


--*/

#include "moufiltr.h"

NTSTATUS DriverEntry (PDRIVER_OBJECT, PUNICODE_STRING);

#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MouFilter_AddDevice)
#pragma alloc_text (PAGE, MouFilter_CreateClose)
#pragma alloc_text (PAGE, MouFilter_IoCtl)
#pragma alloc_text (PAGE, MouFilter_InternIoCtl)
#pragma alloc_text (PAGE, MouFilter_PnP)
#pragma alloc_text (PAGE, MouFilter_Power)
#pragma alloc_text (PAGE, MouFilter_Unload)
#endif

NTSTATUS
DriverEntry (
    IN  PDRIVER_OBJECT  DriverObject,
    IN  PUNICODE_STRING RegistryPath
    )
/*++
Routine Description:

    Initialize the entry points of the driver.

--*/
{
    ULONG i;

    UNREFERENCED_PARAMETER (RegistryPath);

    // 
    // Fill in all the dispatch entry points with the pass through function
    // and the explicitly fill in the functions we are going to intercept
    // 
    for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
        DriverObject->MajorFunction[i] = MouFilter_DispatchPassThrough;
    }

    DriverObject->MajorFunction [IRP_MJ_CREATE] =
    DriverObject->MajorFunction [IRP_MJ_CLOSE] =        MouFilter_CreateClose;
    DriverObject->MajorFunction [IRP_MJ_PNP] =          MouFilter_PnP;
    DriverObject->MajorFunction [IRP_MJ_POWER] =        MouFilter_Power;
    DriverObject->MajorFunction [IRP_MJ_INTERNAL_DEVICE_CONTROL] =
                                                        MouFilter_InternIoCtl;
    //
    // If you are planning on using this function, you must create another
    // device object to send the requests to.  Please see the considerations 
    // comments for MouFilter_DispatchPassThrough for implementation details.
    //
    // DriverObject->MajorFunction [IRP_MJ_DEVICE_CONTROL] = MouFilter_IoCtl;

    DriverObject->DriverUnload = MouFilter_Unload;
    DriverObject->DriverExtension->AddDevice = MouFilter_AddDevice;

    return STATUS_SUCCESS;
}

NTSTATUS
MouFilter_AddDevice(
    IN PDRIVER_OBJECT   Driver,
    IN PDEVICE_OBJECT   PDO
    )
{
    PDEVICE_EXTENSION        devExt;
    IO_ERROR_LOG_PACKET      errorLogEntry;
    PDEVICE_OBJECT           device;
    NTSTATUS                 status = STATUS_SUCCESS;

    PAGED_CODE();

    status = IoCreateDevice(Driver,                   
                            sizeof(DEVICE_EXTENSION), 
                            NULL,                    
                            FILE_DEVICE_MOUSE,    
                            0,                   
                            FALSE,              
                            &device            
                            );

    if (!NT_SUCCESS(status)) {
        return (status);
    }

    RtlZeroMemory(device->DeviceExtension, sizeof(DEVICE_EXTENSION));

    devExt = (PDEVICE_EXTENSION) device->DeviceExtension;
    devExt->TopOfStack = IoAttachDeviceToDeviceStack(device, PDO);
    if (devExt->TopOfStack == NULL) {
        IoDeleteDevice(device);
        return STATUS_DEVICE_NOT_CONNECTED; 
    }

    ASSERT(devExt->TopOfStack);

    devExt->Self =          device;
    devExt->PDO =           PDO;
    devExt->DeviceState =   PowerDeviceD0;

    devExt->SurpriseRemoved = FALSE;
    devExt->Removed =         FALSE;
    devExt->Started =         FALSE;

    device->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE);
    device->Flags &= ~DO_DEVICE_INITIALIZING;

    return status;
}

NTSTATUS
MouFilter_Complete(
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP             Irp,
    IN PVOID            Context
    )
/*++
Routine Description:

    Generic completion routine that allows the driver to send the irp down the 
    stack, catch it on the way up, and do more processing at the original IRQL.
    
--*/
{
    PKEVENT             event;

    event = (PKEVENT) Context;

    UNREFERENCED_PARAMETER(DeviceObject);
    UNREFERENCED_PARAMETER(Irp);

    //
    // We could switch on the major and minor functions of the IRP to perform
    // different functions, but we know that Context is an event that needs
    // to be set.
    //
    KeSetEvent(event, 0, FALSE);

    //
    // Allows the caller to use the IRP after it is completed
    //
    return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS
MouFilter_CreateClose (
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )
/*++
Routine Description:

    Maintain a simple count of the creates and closes sent against this device
    
--*/
{
    PIO_STACK_LOCATION  irpStack;
    NTSTATUS            status;
    PDEVICE_EXTENSION   devExt;

    PAGED_CODE();

    irpStack = IoGetCurrentIrpStackLocation(Irp);
    devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    status = Irp->IoStatus.Status;

    switch (irpStack->MajorFunction) {
    case IRP_MJ_CREATE:
    
        if (NULL == devExt->UpperConnectData.ClassService) {
            //
            // No Connection yet.  How can we be enabled?
            //
            status = STATUS_INVALID_DEVICE_STATE;
        }
        else if ( 1 >= InterlockedIncrement(&devExt->EnableCount)) {
            //
            // First time enable here
            //
        }
        else {
            //
            // More than one create was sent down
            //
        }
    
        break;

    case IRP_MJ_CLOSE:

        ASSERT(0 < devExt->EnableCount);
    
        if (0 >= InterlockedDecrement(&devExt->EnableCount)) {
            //
            // successfully closed the device, do any appropriate work here
            //
        }

        break;
    }

    Irp->IoStatus.Status = status;

    //
    // Pass on the create and the close
    //
    return MouFilter_DispatchPassThrough(DeviceObject, Irp);
}

NTSTATUS
MouFilter_DispatchPassThrough(
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp
        )
/*++
Routine Description:

    Passes a request on to the lower driver.
     
Considerations:
     
    If you are creating another device object (to communicate with user mode
    via IOCTLs), then this function must act differently based on the intended 
    device object.  If the IRP is being sent to the solitary device object, then
    this function should just complete the IRP (becuase there is no more stack
    locations below it).  If the IRP is being sent to the PnP built stack, then
    the IRP should be passed down the stack. 
    
    These changes must also be propagated to all the other IRP_MJ dispatch
    functions (such as create, close, cleanup, etc.) as well!

--*/
{
    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);

    //
    // Pass the IRP to the target
    //
    IoSkipCurrentIrpStackLocation(Irp);
        
    return IoCallDriver(((PDEVICE_EXTENSION) DeviceObject->DeviceExtension)->TopOfStack, Irp);
}           

NTSTATUS
MouFilter_InternIoCtl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This routine is the dispatch routine for internal device control requests.
    There are two specific control codes that are of interest:
    
    IOCTL_INTERNAL_MOUSE_CONNECT:
        Store the old context and function pointer and replace it with our own.
        This makes life much simpler than intercepting IRPs sent by the RIT and
        modifying them on the way back up.
                                      
    IOCTL_INTERNAL_I8042_HOOK_MOUSE:
        Add in the necessary function pointers and context values so that we can
        alter how the ps/2 mouse is initialized.
                                            
    NOTE:  Handling IOCTL_INTERNAL_I8042_HOOK_MOUSE is *NOT* necessary if 
           all you want to do is filter MOUSE_INPUT_DATAs.  You can remove
           the handling code and all related device extension fields and 
           functions to conserve space.
                                         
Arguments:

    DeviceObject - Pointer to the device object.

    Irp - Pointer to the request packet.

Return Value:

    Status is returned.

--*/
{
    PIO_STACK_LOCATION          irpStack;
    PDEVICE_EXTENSION           devExt;
    KEVENT                      event;
    PCONNECT_DATA               connectData;
    PINTERNAL_I8042_HOOK_MOUSE  hookMouse;
    NTSTATUS                    status = STATUS_SUCCESS;

    devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    Irp->IoStatus.Information = 0;
    irpStack = IoGetCurrentIrpStackLocation(Irp);

    switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {

    //
    // Connect a mouse class device driver to the port driver.
    //
    case IOCTL_INTERNAL_MOUSE_CONNECT:
        //
        // Only allow one connection.
        //
        if (devExt->UpperConnectData.ClassService != NULL) {
            status = STATUS_SHARING_VIOLATION;
            break;
        }
        else if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
                sizeof(CONNECT_DATA)) {
            //
            // invalid buffer
            //
            status = STATUS_INVALID_PARAMETER;
            break;
        }

        //
        // Copy the connection parameters to the device extension.
        //
        connectData = ((PCONNECT_DATA)
            (irpStack->Parameters.DeviceIoControl.Type3InputBuffer));

        devExt->UpperConnectData = *connectData;

        //
        // Hook into the report chain.  Everytime a mouse packet is reported to
        // the system, MouFilter_ServiceCallback will be called
        //
        connectData->ClassDeviceObject = devExt->Self;
        connectData->ClassService = MouFilter_ServiceCallback;

        break;

    //
    // Disconnect a mouse class device driver from the port driver.
    //
    case IOCTL_INTERNAL_MOUSE_DISCONNECT:

        //
        // Clear the connection parameters in the device extension.
        //
        // devExt->UpperConnectData.ClassDeviceObject = NULL;
        // devExt->UpperConnectData.ClassService = NULL;

        status = STATUS_NOT_IMPLEMENTED;
        break;

    //
    // Attach this driver to the initialization and byte processing of the 
    // i8042 (ie PS/2) mouse.  This is only necessary if you want to do PS/2
    // specific functions, otherwise hooking the CONNECT_DATA is sufficient
    //
    case IOCTL_INTERNAL_I8042_HOOK_MOUSE:   

        if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
                 sizeof(INTERNAL_I8042_HOOK_MOUSE)) {
            //
            // invalid buffer
            //
            status = STATUS_INVALID_PARAMETER;
            break;
        }

        //
        // Copy the connection parameters to the device extension.
        //
        hookMouse = (PINTERNAL_I8042_HOOK_MOUSE)
            (irpStack->Parameters.DeviceIoControl.Type3InputBuffer);

        //
        // Set isr routine and context and record any values from above this driver
        //
        devExt->UpperContext = hookMouse->Context;
        hookMouse->Context = (PVOID) DeviceObject;

        if (hookMouse->IsrRoutine) {
            devExt->UpperIsrHook = hookMouse->IsrRoutine;
        }
        hookMouse->IsrRoutine = (PI8042_MOUSE_ISR) MouFilter_IsrHook;

        //
        // Store all of the other functions we might need in the future
        //
        devExt->IsrWritePort = hookMouse->IsrWritePort;
        devExt->CallContext = hookMouse->CallContext;
        devExt->QueueMousePacket = hookMouse->QueueMousePacket;

        break;

    //
    // These internal ioctls are not supported by the new PnP model.
    //
#if 0       // obsolete
    case IOCTL_INTERNAL_MOUSE_ENABLE:
    case IOCTL_INTERNAL_MOUSE_DISABLE:
        status = STATUS_NOT_SUPPORTED;
        break;
#endif  // obsolete

    //
    // Might want to capture this in the future.  For now, then pass it down
    // the stack.  These queries must be successful for the RIT to communicate
    // with the mouse.
    //
    case IOCTL_MOUSE_QUERY_ATTRIBUTES:
    default:
        break;
    }

    if (!NT_SUCCESS(status)) {
        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        return status;
    }

    return MouFilter_DispatchPassThrough(DeviceObject, Irp);
}

NTSTATUS
MouFilter_PnP(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This routine is the dispatch routine for plug and play irps 

Arguments:

    DeviceObject - Pointer to the device object.

    Irp - Pointer to the request packet.

Return Value:

    Status is returned.

--*/
{
    PDEVICE_EXTENSION           devExt; 
    PIO_STACK_LOCATION          irpStack;
    NTSTATUS                    status = STATUS_SUCCESS;
    KIRQL                       oldIrql;
    KEVENT                      event;        

    PAGED_CODE();

    devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    irpStack = IoGetCurrentIrpStackLocation(Irp);

    switch (irpStack->MinorFunction) {
    case IRP_MN_START_DEVICE: {

        //
        // The device is starting.
        //
        // We cannot touch the device (send it any non pnp irps) until a
        // start device has been passed down to the lower drivers.
        //
        IoCopyCurrentIrpStackLocationToNext(Irp);
        KeInitializeEvent(&event,
                          NotificationEvent,
                          FALSE
                          );

        IoSetCompletionRoutine(Irp,
                               (PIO_COMPLETION_ROUTINE) MouFilter_Complete, 
                               &event,
                               TRUE,
                               TRUE,
                               TRUE); // No need for Cancel

        status = IoCallDriver(devExt->TopOfStack, Irp);

        if (STATUS_PENDING == status) {
            KeWaitForSingleObject(
               &event,
               Executive, // Waiting for reason of a driver
               KernelMode, // Waiting in kernel mode
               FALSE, // No allert
               NULL); // No timeout
        }

        if (NT_SUCCESS(status) && NT_SUCCESS(Irp->IoStatus.Status)) {
            //
            // As we are successfully now back from our start device
            // we can do work.
            //
            devExt->Started = TRUE;
            devExt->Removed = FALSE;
            devExt->SurpriseRemoved = FALSE;
        }

        //
        // We must now complete the IRP, since we stopped it in the
        // completetion routine with MORE_PROCESSING_REQUIRED.
        //
        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        break;
    }

    case IRP_MN_SURPRISE_REMOVAL:
        //
        // Same as a remove device, but don't call IoDetach or IoDeleteDevice
        //
        devExt->SurpriseRemoved = TRUE;

        // Remove code here

        IoSkipCurrentIrpStackLocation(Irp);
        status = IoCallDriver(devExt->TopOfStack, Irp);
        break;

    case IRP_MN_REMOVE_DEVICE:
        
        devExt->Removed = TRUE;

        // remove code here
        Irp->IoStatus.Status = STATUS_SUCCESS;

        IoSkipCurrentIrpStackLocation(Irp);
        status = IoCallDriver(devExt->TopOfStack, Irp);

        IoDetachDevice(devExt->TopOfStack); 
        IoDeleteDevice(DeviceObject);

        break;

    case IRP_MN_QUERY_REMOVE_DEVICE:
    case IRP_MN_QUERY_STOP_DEVICE:
    case IRP_MN_CANCEL_REMOVE_DEVICE:
    case IRP_MN_CANCEL_STOP_DEVICE:
    case IRP_MN_FILTER_RESOURCE_REQUIREMENTS: 
    case IRP_MN_STOP_DEVICE:
    case IRP_MN_QUERY_DEVICE_RELATIONS:
    case IRP_MN_QUERY_INTERFACE:
    case IRP_MN_QUERY_CAPABILITIES:
    case IRP_MN_QUERY_DEVICE_TEXT:
    case IRP_MN_QUERY_RESOURCES:
    case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
    case IRP_MN_READ_CONFIG:
    case IRP_MN_WRITE_CONFIG:
    case IRP_MN_EJECT:
    case IRP_MN_SET_LOCK:
    case IRP_MN_QUERY_ID:
    case IRP_MN_QUERY_PNP_DEVICE_STATE:
    default:
        //
        // Here the filter driver might modify the behavior of these IRPS
        // Please see PlugPlay documentation for use of these IRPs.
        //
        IoSkipCurrentIrpStackLocation(Irp);
        status = IoCallDriver(devExt->TopOfStack, Irp);
        break;
    }

    return status;
}

NTSTATUS
MouFilter_Power(
    IN PDEVICE_OBJECT    DeviceObject,
    IN PIRP              Irp
    )
/*++

Routine Description:

    This routine is the dispatch routine for power irps   Does nothing except
    record the state of the device.

Arguments:

    DeviceObject - Pointer to the device object.

    Irp - Pointer to the request packet.

Return Value:

    Status is returned.

--*/
{
    PIO_STACK_LOCATION  irpStack;
    PDEVICE_EXTENSION   devExt;
    POWER_STATE         powerState;
    POWER_STATE_TYPE    powerType;

    PAGED_CODE();

    devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    irpStack = IoGetCurrentIrpStackLocation(Irp);

    powerType = irpStack->Parameters.Power.Type;
    powerState = irpStack->Parameters.Power.State;

    switch (irpStack->MinorFunction) {
    case IRP_MN_SET_POWER:
        if (powerType  == DevicePowerState) {
            devExt->DeviceState = powerState.DeviceState;
        }

    case IRP_MN_QUERY_POWER:
    case IRP_MN_WAIT_WAKE:
    case IRP_MN_POWER_SEQUENCE:
    default:
        break;
    }

    PoStartNextPowerIrp(Irp);
    IoSkipCurrentIrpStackLocation(Irp);
    return PoCallDriver(devExt->TopOfStack, Irp);
}

BOOLEAN
MouFilter_IsrHook (
    PDEVICE_OBJECT          DeviceObject, 
    PMOUSE_INPUT_DATA       CurrentInput, 
    POUTPUT_PACKET          CurrentOutput,
    UCHAR                   StatusByte,
    PUCHAR                  DataByte,
    PBOOLEAN                ContinueProcessing,
    PMOUSE_STATE            MouseState,
    PMOUSE_RESET_SUBSTATE   ResetSubState
)
/*++

Remarks:
    i8042prt specific code, if you are writing a packet only filter driver, you
    can remove this function

Arguments:

    DeviceObject - Our context passed during IOCTL_INTERNAL_I8042_HOOK_MOUSE
    
    CurrentInput - Current input packet being formulated by processing all the
                    interrupts

    CurrentOutput - Current list of bytes being written to the mouse or the
                    i8042 port.
                    
    StatusByte    - Byte read from I/O port 60 when the interrupt occurred                                            
    
    DataByte      - Byte read from I/O port 64 when the interrupt occurred. 
                    This value can be modified and i8042prt will use this value
                    if ContinueProcessing is TRUE

    ContinueProcessing - If TRUE, i8042prt will proceed with normal processing of
                         the interrupt.  If FALSE, i8042prt will return from the
                         interrupt after this function returns.  Also, if FALSE,
                         it is this functions responsibilityt to report the input
                         packet via the function provided in the hook IOCTL or via
                         queueing a DPC within this driver and calling the
                         service callback function acquired from the connect IOCTL
                                             
Return Value:

    Status is returned.

  --+*/
{
    PDEVICE_EXTENSION   devExt;
    BOOLEAN             retVal = TRUE;

    devExt = DeviceObject->DeviceExtension;

    if (devExt->UpperIsrHook) {
        retVal = (*devExt->UpperIsrHook) (
            devExt->UpperContext,
            CurrentInput,
            CurrentOutput,
            StatusByte,
            DataByte,
            ContinueProcessing,
            MouseState,
            ResetSubState
            );

        if (!retVal || !(*ContinueProcessing)) {
            return retVal;
        }
    }

    *ContinueProcessing = TRUE;
    return retVal;
}

VOID
MouFilter_ServiceCallback(
    IN PDEVICE_OBJECT DeviceObject,
    IN PMOUSE_INPUT_DATA InputDataStart,
    IN PMOUSE_INPUT_DATA InputDataEnd,
    IN OUT PULONG InputDataConsumed
    )
/*++

Routine Description:

    Called when there are mouse packets to report to the RIT.  You can do 
    anything you like to the packets.  For instance:
    
    o Drop a packet altogether
    o Mutate the contents of a packet 
    o Insert packets into the stream 
                    
Arguments:

    DeviceObject - Context passed during the connect IOCTL
    
    InputDataStart - First packet to be reported
    
    InputDataEnd - One past the last packet to be reported.  Total number of
                   packets is equal to InputDataEnd - InputDataStart
    
    InputDataConsumed - Set to the total number of packets consumed by the RIT
                        (via the function pointer we replaced in the connect
                        IOCTL)

Return Value:

    Status is returned.

--*/
{
    PDEVICE_EXTENSION   devExt;

    devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    //
    // UpperConnectData must be called at DISPATCH
    //
    (*(PSERVICE_CALLBACK_ROUTINE) devExt->UpperConnectData.ClassService)(
        devExt->UpperConnectData.ClassDeviceObject,
        InputDataStart,
        InputDataEnd,
        InputDataConsumed
        );
}

VOID
MouFilter_Unload(
   IN PDRIVER_OBJECT Driver
   )
/*++

Routine Description:

   Free all the allocated resources associated with this driver.

Arguments:

   DriverObject - Pointer to the driver object.

Return Value:

   None.

--*/

{
    PAGED_CODE();

    UNREFERENCED_PARAMETER(Driver);

    ASSERT(NULL == Driver->DeviceObject);
}


First interesting line was this:

        devExt->QueueMousePacket = hookMouse->QueueMousePacket;

Suggests there is a specific callback method that may be usable to inject packets into the stream, at such a location that they will be processed correctly, I'm not sure.

Next interesting piece of code is this:

VOID
MouFilter_ServiceCallback(
    IN PDEVICE_OBJECT DeviceObject,
    IN PMOUSE_INPUT_DATA InputDataStart,
    IN PMOUSE_INPUT_DATA InputDataEnd,
    IN OUT PULONG InputDataConsumed
    )
/*++

Routine Description:

    Called when there are mouse packets to report to the RIT.  You can do 
    anything you like to the packets.  For instance:
    
    o Drop a packet altogether
    o Mutate the contents of a packet 
    o Insert packets into the stream 
                    
Arguments:

    DeviceObject - Context passed during the connect IOCTL
    
    InputDataStart - First packet to be reported
    
    InputDataEnd - One past the last packet to be reported.  Total number of
                   packets is equal to InputDataEnd - InputDataStart
    
    InputDataConsumed - Set to the total number of packets consumed by the RIT
                        (via the function pointer we replaced in the connect
                        IOCTL)

Return Value:

    Status is returned.

--*/

This suggests that we could "Insert packets into the stream". I'm not sure how this method is called, if it's called constantly, or just when input is in the stream. If it's only when input is in the stream, then we couldn't really use it to inject new input unless other input has been generated to call the method first. This is where the QueueMousePacket may call the service callback.

Anyways, that's the extent of my research today, my head hurts trying to absorb all the new stuff involved in writing device drivers. Maybe someone else can piece something together from these ideas?

Good luck.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Thanks for the code.

By the way, have you tried "SetMouseDelay, 100" (or some high number) with the game? If it's polling the mouse, that might do the trick

You've probably already thought of this. I'm just mentioning it again in case there's something simple that was overlooked.

Astaelan
  • Members
  • 34 posts
  • Last active: Dec 13 2007 03:39 AM
  • Joined: 23 Sep 2004
Yep, tried that when you mentioned it previously. Tried both with individual events, using sleep and the way you recommended with the mousedelay and separated strokes in the MouseClick.

No luck, if they are polling, then they are using WH_LL hooks and I think they are somehow keeping their hook at the top of the chain. That's the only reason I can see for it to ignore the events we stripped of the injected bit. If it's polling, I should have had my hooks with madCodeHook called when attempting to hook all the GetKeyState, GetKeyboardState, GetAsyncKeyState or whatever... I can't even remember all the things I hooked anymore, but I tried a lot of them. The only ever called was GetKeyState, for CONTROL/SHIFT codes as we determined, values 16 and 17 only passed through it.

Anyway, I think the code I pasted holds the answer to the puzzle, but I'm still a bit daunted with the code and writing the driver in such a way that it could be tested without a whole bunch of code to actually turn on and off the filter, to inject events, etc.

Anyway, maybe someone will feel ambitious, pull out the DDK and hack something together. I'm just too burnt out these days to give it a good effort. But I'm sure I'll get tired enough of clicking again soon to try.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004

I think the code I pasted holds the answer to the puzzle, but I'm still a bit daunted

I looked it over and I can see why: its not well explained, perhaps because it is documented elsewhere (maybe in a Microsoft Press book of some kind).

BoBo
  • Guests
  • Last active:
  • Joined: --

Anyway, maybe someone will feel ambitious, pull out the DDK and hack something together


Would it make sense to censor that post back to its initial topic (a wish), and extract the "in detail" technical/support part of this discussion to be moved to the support forum :?:

Hopefully one of the Game+Code genius' from outer space will be aware of it before Astaelan even think about to comit suicide because of a game ... :shock: :roll: :wink:

That's MHO. Thx for your interest.

8)

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
I think it used to be in Support but was moved here.

Since splitting the topic would lose the original headers and format, it seems best to keep it all here unless you see a large benefit to making a new topic in Support that points to this one.

Astaelan
  • Members
  • 34 posts
  • Last active: Dec 13 2007 03:39 AM
  • Joined: 23 Sep 2004
Agreed, leave the topic as wish list item. When Chris turns his focus towards this issue, we can start a new discussion topic under support again for testing and reporting results.

And thanks for your concern, Bobo, hasn't quite been suicide yet, but I've almost pitched my AMD64 through the window :)

None the less, nothing new to report, and I do hope a game designer out there will throw in their 2 cents on the matter sometime soon.

Ciao for now.

Astaelan
  • Members
  • 34 posts
  • Last active: Dec 13 2007 03:39 AM
  • Joined: 23 Sep 2004
Hey Chris, I'm back again.

I was working on the DDK stuff when a friend suggested something else that is almost a solution.

In the past I mentioned DeviceIoControl but I wasn't sure on how to use it. As it stands, it was somewhat explained to be, but documentation remains scarce on what I need to know.

So here's what I figured out so far... By calling DefineDosDevice, you can create a virtual device linked to the original class devices, as so:

DefineDosDevice(DDD_RAW_TARGET_PATH, "Mouse", "\\Device\\PointerClass0")

Now, the system will recognize the device "\\.\Mouse", and can be used in a call to CreateFile to create a HANDLE to a stream to the mouse. So far I believe this is correct, so here is the code for CreateFile:

m_hMouse = CreateFile("\\\\.\\Mouse", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)

So now, provided m_hMouse is okay, we have a handle to the mouse device that can be passed to DeviceIoControl. This is where documentation is scarce and I have not been able to figure out the rest. However, here are 2 simple methods that I've used to initialize and close the mouse handles in a simple MFC application:

bool CPTBotDlg::InitializeMouse()
{
	bool bMouse = false;
	if(DefineDosDevice(DDD_RAW_TARGET_PATH, "Mouse", "\\Device\\PointerClass0"))
		if((m_hMouse = CreateFile("\\\\.\\Mouse", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)) != INVALID_HANDLE_VALUE)
			bMouse = true;
	return bMouse;
}


void CPTBotDlg::UninitializeMouse()
{
	if(DefineDosDevice(DDD_REMOVE_DEFINITION, "Mouse", NULL))
		if(m_hMouse != INVALID_HANDLE_VALUE)
			CloseHandle(m_hMouse);
	m_hMouse = INVALID_HANDLE_VALUE;
}

m_hMouse is defined as a HANDLE.

So, the next step I think is to write a method that uses the handle calling DeviceIoControl, and here's what I know about that:

DeviceIoControl(m_hMouse, CTL_CODE(FILE_DEVICE_MOUSE,  0x802, METHOD_BUFFERED, FILE_ANY_ACCESS), NULL, 0, NULL, 0, NULL, NULL)

This is PURELY a non functional example, however it demonstrates the little bit I've pieced together so far. First, m_hMouse, as we described above, is passed in. Second, we have to create an IO Control Code to be passed to the device. In this case, we want to create a device io control code for writing mouse clicks to the data stream. So, the first argument we use is FILE_DEVICE_MOUSE, and I'm about 80% sure about this argument being correct. Next, we have 0x802, and this I'm only about 10% sure on, as I just stumbled across a reference to CTL_CODE on some chinese driver forum, and the only english parts were that 0x801 referred to mouse movement, and 0x802 referred to mouse clicks. So again, not able to find anything clear on this matter. Next comes METHOD_BUFFERED and FILE_ANY_ACCESS which are the typically suggested default arguments to use.
After we can figure out if this is a proper control code, we are still stuck with another problem. Every control code takes a different input and output buffer argument, typecased to LPVOID. So this leaves a big guessing game as to what type of input buffer to allocate. The MSDN only has a short bit to say on DeviceIoControl, that you can pass NULL for the buffers if they are not required for the IO Control Code. My guess is that only the input buffer will be required. That is the first 2 arguments after the control code, an LPVOID to the buffer, and the size of the buffer. The following 4 arguments are the output buffer, size, a pointer to how many bytes are put into the output buffer, and a pointer to any overlapped data. I do not believe, at this point, that the last 4 arguments are of concern, however, I must figure out what sort of input buffer to pass in, and what data to put in the buffer, in order to get mouse movements and clicks to work.

I realize you're probably still tied up with features on the todo list, but I hope this might renew your interest enough to help dig for the missing pieces of information. If this works, then it will negate the need for a separate system driver, as DeviceIoControl can be called from AutoHotkey directly, just have the directive used, take an optional argument for the class devices (some people having 2 mice, would maybe use \\Device\PointerClass1 instead of PointerClass0, as such, allow them to provide a device number to hook, but this is far off still).

My calls to Initialize both Keyboard and Mouse work successfully, I believe my handles are valid, however I have no idea how to use DeviceIoControl to feed input into the device stream. My believe is that once it's in there, like the Kernel level equivilent, it will call the appropriate callbacks to handle the data. I found a small diagram I forgot to bookmark, that showed the different layers for input abstraction, where DirectInput and other Legacy methods were quite high level, and the mouse class, and mouse filter drivers, and DeviceIoControl's point of communication was quite a bit lower, almost at the physical data port itself. If I am correct, DeviceIoControl injects data into the moufiltr level, and from there it's passed to the mouclass, and on upwards to DirectInput and Legacy methods.

Any help on this subject would be greatly appreciated if anyone can even point me in the right direction. I've spent a good number of hours searching google and talking with someone who wrote a Virtual Volume mounting driver using the DDK, and it really seems the information on DeviceIoControl is limited to what you find at MSDN, and they only suggest about 30 different FILE_DEVICE values, that have different input and output buffer arguments, but then never document any of it. This isn't even really something you can guess and test with, I really have NO idea what kind of buffer to allocate, if I'm even doing this right. DeviceIoControl simply just fails right now as could be expected.

Oh well, that's all I have to report, hope somthing sparks a movement forward on this... As you can see, I haven't quite given up yet :)

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Since by now you have far more knowledge on the subject than I do, all I can offer is some more Internet searches.

You've probably seen these already, but they seem relevant so I'll list them here just in case you haven't:

Gives an example of a driver and it seems to talk about generating driver-level events:
http://www.fz-juelic... ... NT4DRV.pdf

This is a French page about Windows CE, but there might be something of interest: http://www.laboratoi... ... ce_driver/

Talks about interrupt hooking (possibly not relevant, except for background): http://www.codeproje... ... ect=662349

I expect there is a MS Press book written on this subject, but even if you could discover its title and be willing to buy it, it might not contain the specific solutions needed.

Astaelan
  • Members
  • 34 posts
  • Last active: Dec 13 2007 03:39 AM
  • Joined: 23 Sep 2004
As you suggested, there is a book released by Microsoft, called Programming the Microsoft Driver Development Kit, or something like that.

And, as you hinted, I'm not too eager to purchase the book for the tidbit of information I'm after. However, as seems to be my curse, no sooner did I post to the forum, and my direction was set straight again.

I found that calling IOCTL_MOUSE_INSERT_DATA was setting the last error retrievable by GetLastError, it turned out that mouclass.sys does not actually implement IOCTL_MOUSE_INSERT_DATA. This prompted me to go searching through the DDK's mouclass example. After digging through the code a bit, I found reference to IOCTL_MOUSE_QUERY_ATTRIBUTES which was defined at the same place as IOCTL_MOUSE_INSERT_DATA. This gave me some hope.

I have since recompiles the mouclass.sys, updated the registry and have it loading a new mo2class.sys. Furthermore, I've added the IOCTL_MOUSE_INSERT_DATA case into the switch, and it no longer returns an "Incorrect function." error. It actually crashes now with unhandled exception, because I used my best educated guess to fill in some basic success return code without doing anything in the driver, but that didn't work so well.

So, my next step, probably going to attempt today, is to edit the mouclass.sys again, and try to get the driver to have DeviceIoControl basically return success, without doing anything, as opposed to returning the default unimplemented/unhandled error. Once I can figure out if I have to make any special calls before returning, then I think I know where to insert mouse data in this specific case. This method in the driver, passes in a DriverObject, from which a DriverExtension object can be obtained. And from the DriverExtension, you can directly manipulate the InputData, DataIn, and DataOut values, which I believe in the case of the mouse, contain MOUSE_INPUT_DATA structures.

So, my theory is that once I can have the IOCTL_MOUSE_INSERT_DATA return without crashing, then I can try pushing MOUSE_INPUT_DATA structure packets to the top of the input data. After looking at mouclass.c, there is a method MouseServiceCallback or something like that, which takes a couple pointers for the input data, but it's purpose is to move data from port buffer to class buffer. If the arguments are the port buffer pointers, then I could theoretically call the service callback to take a couple pointers and push the data into the class buffer without having any special work to do other than creating the MOUSE_INPUT_DATA structure and calling the callback method myself.

I'm starting to piece this together, with much thanks to a friend on IRC who has been patient enough to explain some of what he could, but unfortunately, as he put it, I seem to be breaking ground that only people hired by Microsoft typically do.

Thanks for the links, I've read most of them as you thought. My research has been exhausting, and I've checked out every website with any reference to any unique keyword I could think of. At this point now, I believe it's a matter of guess and test, because I think I have the correct solution, just need to implement it.

However, unlike my previous post which suggested a driver would not be needed, in this case you do need to replace your mouclass.sys, because it doesn't support the INSERT_DATA IOCTL. Ideally however, I'd like to write an INF to autoinstall it, but I've found that editing a few registry settings and rebooting works just the same, so for a temporary solution I've just created a registry patch to update when I change my mouclass.sys file (because while it's loaded, it's protected by windows, if you rename, delete, or copy over it, it reappears as the original). So I just create a new filename each time like moiclass.sys or mo2class.sys, mo3class.sys, whatever, and update the registry, works great. Proven that it is loading by running a drivers.exe that comes with the DDK, and shows which drivers are loaded. kbdclass.sys and mo2class.sys are currently active. I'm happy to see that much works, now just to get the driver to support INSERT_DATA and I believe we'll have a solution.

As you previously suggesting, the installation of a new mouclass.sys is somewhat detrimental. It could be possible if someone uses special mouse/keyboard drivers that the original mouclass.sys has been replaced by the custom driver. But, on the bright side, the mouclass.sys I'm creating is an exact duplicate of the mouclass.sys that ships with Windows 2000, and so it should function just as the standard driver with the exception that DeviceIoControl will be able to call INSERT_DATA.

Turned out to be a 2-point solution, both writing mouclass.sys update, and an EXE to access it via DeviceIoControl, but it also seems to be the cleanest solution I've considered, in that the entire injection code will be self contained into a few lines of code added to mouclass.sys and the entire intelligent scripting host can be running in the EXE, much like AutoHotkey.

And as a completely off topic wish-list request, it may already exist, and I just hadn't run into it, but could you look into supporting container variables? At the very least, a dynamic linked list of strings. Something you can use like a dynamic array...

ListAdd, strList, 'SomeNewString'
ListRemove, strList, index
ListSearch, strList, 'string', returnedindex
ListCount, strList, returnedcount
ListIterator, strList, returnediterator
ListIterate, strList, iterator
ListValue, strList, iterator, returnedstring

Something like that would be pretty useful in long term complicated scripts, but I appologize if something similar exists that I missed.
In fact, as a non-programmer, the lack of type definition is probably easier to learn initially, but after talking to my IRC friend, who happened to try AutoHotkey, he too said that the limited type of variables had him fumbling with an otherwise simple script language.

By the way, that friend ended up using quite an ingenious solution himself. He had an X45 Saitek joystick. The software for it comes with 2 virtual drivers to add to the driver stack, for keyboard and mouse, which allows the joystick to be programmed to react like the mouse or keyboard. More interestingly, the software supported chain of events, so you could setup repeated chains to simulate mouse and keyboard.

Ingenious, and I believe that my idea works the same way. Either they added a driver at a different level, or they simply replace mouclass and kbdclass to support inserted data just like I am trying to do.

Hey Chris, I know you are a pretty busy guy, but if you were willing to get in touch on ICQ or MSN, maybe you could provide some more realtime feedback as I'm trying to figure this out. Thank you for assuming I know more on this subject, but really right now I'm stumbling through with scraps of information. I was overjoyed when I could simply open the mouse and keyboard handles successfully, heh. Last night's install of mouclass.sys successfully was another big success to me. And it's only taken a month! Haha.