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.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.
A program that defeats AutoHotKey???
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.
Thanks for sharing that.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.
I think Savage was saying that SendInput() didn't work, but keybd_event() did.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.
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.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.
Thanks for the info. I'll try to research those leads sometime in the next few weeks.That said, Chris, here's all the information I can provide at this time. Sorry it isn't much.
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
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
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.
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.
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.
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).I think the code I pasted holds the answer to the puzzle, but I'm still a bit daunted
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)
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.
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.
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
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.
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.