The Act LabsTM Light Gun Compatibility Patch
How It Works
Technically speaking, the Troubleshooter is not a "patch" in the traditional sense. It does not fix anything, as these games are not broken to begin with. Nor does it replace or modify the games' files in any way.
The Troubleshooter uses a CBT hook to attach itself to a game at run-time, then accomplishes two things:
1) It reads input from the light guns, and "force-feeds" the absolute coordinates into the game (more on this below).
2) It intercepts the game's calls to the DirectInput or WinUser API. In doing so, it can then manipulate the information returned from the API and prevent the game from detecting certain events (such as mouse movement) and can also inject "fake" events (such as keystrokes) into the game.
Handling Light Gun Input
Handling the input from a single light gun is easy. A standard Windows Mouse Hook can do the trick.
Handling the input from two TV USB Light Guns is also easy, thanks to their Player 1/Player 2 switch. When two TV guns are connected, they are treated like a single 4-button mouse. When the switch is set for Player 1, the gun reports button 0 (left click) for the trigger, and button 1 (right click) for reloading. When the switch is set for Player 2, the gun reports buttons 2 and 3 instead. So again, a standard Windows Mouse Hook can be used to handle the input from both guns.
For two PC USB Light Guns however, things are a little more complicated. Since the PC USB Light Gun has no switch to change the button assignments, a Mouse Hook has no way of distinguishing between multiple guns. To determine which gun was fired, a more low-level mechanism must be used to handle the input.
In Windows 98/ME, DirectX can be used to read distinct input from multiple individual mice (USB devices only). But for reasons known only to Microsoft, this ability is absent from Windows 2000/XP - only the virtual "SysMouse" is accessible via DirectX.
In Windows XP, Microsoft introduced a new way to read low-level device data, called "Raw Input", which does provide the means to distinguish between multiple mice. So, the Troubleshooter can support multiple PC USB Light Guns on all platforms except Windows 2000, which does not feature either mechanism.
Force-Feeding Absolute Coordinates to the Game
Even though the Troubleshooter can manipulate the input sent to the game (see below), it has no control over what the game actually does with that input. There is no way to force it to interpret mouse data as absolute rather than relative. However, there is one thing we can count on: When handling relative input, the game needs to know "relative to what". So, somewhere in the game's memory, there must be a set of variables that keep track of the current, absolute positions of the players' gun sights. And if we know the memory address of those variables, we can overwrite their contents, effectively telling the game where the gun sights ought to be.
And this is exactly what the Troubleshooter does. The memory addresses of the players' gun coordinates, for each of the supported games, are stored in the TRBLSHTR.INI file. As luck would have it, those addresses remain fixed for all supported games (except Die Hard Trilogy 2, for which the addresses must be determined programmatically at run-time).
Finding those addresses involved a lot of tedious reverse engineering, debugging, and memory searching. It was further complicated by the fact that each game stores its coordinates in a different data type, in a different scale, and with a different origin. But the result was worth the effort.
When the player fires a light gun, the Troubleshooter reads the gun's absolute coordinates, converts them to the appropriate scale and data type for the game, and writes them into the game's memory. It then injects a keystroke for the player's "Fire" key. And voila! The shot is fired and goes exactly where it was supposed to.
With all but one exception (Virtua Squad 1), the currently supported games use DirectInput to read from the keyboard. Unlike the WinUser API, DirectInput provides no official mechanism for intercepting or injecting keystrokes programmatically (after all, it is supposed to be direct input, isn't it?). So functions like SendInput and keybd_event cannot be used to simulate keystrokes in these games.
Instead, the Troubleshooter injects keystrokes into the games by wedging itself in between the games and the DirectInput API, and manipulating the information that DirectInput returns.
Using the Function Interception technique described here, the game's initial call to DirectInputCreate gets intercepted. Internally, the Troubleshooter calls the real function, but creates a wrapper object around the resulting IDirectInput instance. The game receives the wrapper instead of the real thing, but since the interfaces are identical, it does not know the difference.
The DirectInputWrapper object intercepts calls to the IDirectInput::CreateDevice method. When the game calls this method to obtain a handle to the keyboard device, the Troubleshooter provides yet another wrapper object, this time around the IDirectInputDevice instance representing the keyboard.
The KeyboardWrapper intercepts calls to the IDirectInputDevice::GetDeviceState method and it is here that keystrokes can be injected by manipulating the array of key states returned to the game.
The Troubleshooter was written to work with specific versions of these games. There may be slightly different versions of these games released around the world, or by different publishers. There may also exist patches for some of the games that include updated EXE files. The Troubleshooter includes support for a few known variants of the games, but there may still be some variants that are not yet suppported (because their internal memory addresses for the gun coordinates may be different).
At best, the gun simply won't work. At worst, the game will crash on startup. So far, I have successfully updated this patch whenever I have encountered a new variant of one of the supported games. If you encounter a new variant that does not work with any of the existing configurations, please contact me. I make no promises, but if I can, I will add a new configuration to support it.