Windows: How to create custom appcompat shims (Application Fixes)?
I'm not aware of any way of someone other than Microsoft implementing an appcompat shim.
You might want to investigate Detours, it might provide the functionality you want.
It is possible to create custom real application compatibility shims applied with the real shim engine, but doing so is definitely not supported by Microsoft. Raymond Chen would be horrified.
Creating the shim
Shim modules are DLLs that export two functions:
// Called by the shim engine to request the installation of a shim.
// Returns an array of requested import hooks and provides its length in the pdwHookCount output parameter.
EXTERN_C PHOOKAPI WINAPI GetHookAPIs(LPSTR pszArgs, LPWSTR pwszShim, PDWORD pdwHookCount);
// Called by the shim engine at various points in the process's life cycle.
EXTERN_C VOID WINAPI NotifyShims(DWORD notification, PVOID data);
// Represents a request for an imported function to be redirected to one in this DLL.
// Definition based on information from the ReactOS project.
typedef struct tagHOOKAPI {
PCSTR DllName;
PCSTR FunctionName;
PVOID HookFunction;
PVOID NextFunction; // Populated by the shim engine
PVOID Reserved[2];
} HOOKAPI, *PHOOKAPI;
For a simple shim module that just needs to provide one shim that replaces one Win32 function, your GetHookAPIs
implementation can just check pwszShim
against your one shim's name, set *pdwHookCount
to 1, and return a pointer to a HOOKAPI
structure describing the target function and its replacement. The hook function whose address you put in the HookFunction
field will be called instead of the target. It can use the NextFunction
field, which will by then have been set by the shim engine, to call the real/original function when appropriate. NotifyShims
can be a no-op.
Alternatively, I have provided some scaffolding in my custom shim development kit which will be especially helpful if you need to write shim modules containing multiple or more complex shims. I'll demonstrate it making your desired TSLie
shim. Starting from the CustomShim
project there, let's create Shim_TSLie.h
to declare a class for a simple shim:
#pragma once
#include "Shim.h"
class Shim_TSLie : public Shim {
public:
Shim_TSLie();
protected:
virtual void RegisterHooks();
private:
static int WINAPI Hook_GetSystemMetrics(int nIndex);
};
In Shim_TSLie.cpp
, let's implement the class, adding a hook on GetSystemMetrics
(which MSDN says is in User32.dll
) that checks the requested metric and returns zero for SM_REMOTESESSION
:
#include "Shim_TSLie.h"
SHIM_INSTANCE(TSLie)
void Shim_TSLie::RegisterHooks() {
ADD_HOOK("USER32.DLL", "GetSystemMetrics", Hook_GetSystemMetrics);
}
int WINAPI Shim_TSLie::Hook_GetSystemMetrics(int nIndex) {
if (nIndex == SM_REMOTESESSION) {
ASL_PRINTF(ASL_LEVEL_TRACE, "Lie: returning 0 for SM_REMOTESESSION");
return 0;
} else {
// Get and call the real GetSystemMetrics
DEFINE_NEXT(Hook_GetSystemMetrics);
return next(nIndex);
}
}
Edit shimlist.cpp
to include and instantiate Shim_TSLie
, maybe delete the other example shims, and compile in the Release x86 configuration.
Installing the shim module
On Windows 10, the shim engine will only load shim modules with certain names. I recommend renaming the DLL AcRes.dll
since that's an accepted name not used for an existing shim module. After any necessary rename, copy the DLL into the C:\Windows\SysWOW64
folder.
Applying the shim
Now for the hard part. The Compatibility Administrator doesn't let you define shims and doesn't look for shims in non-system databases, so the SDB file that declares and uses the custom shim must be created by other means.
Method 1: ShimDBC
If you get ahold of ShimDBC, you can write XML like this, changing the EXE
attributes appropriately and adding any other matching information attributes you need...
<?xml version="1.0" encoding="utf-8"?>
<DATABASE NAME="Custom Database">
<LIBRARY>
<SHIM NAME="TSLie" FILE="AcRes.dll" RUNTIME_PLATFORM="X86_ANY"/>
</LIBRARY>
<APP NAME="Some App">
<EXE NAME="SomeApp.exe" PRODUCT_NAME="Some Product" RUNTIME_PLATFORM="X86_ANY">
<SHIM NAME="TSLie"/>
</EXE>
</APP>
</DATABASE>
...and compile it like so, changing the YourXml.xml
and YourDatabase.sdb
filenames as you like:
shimdbc Custom YourXml.xml YourDatabase.sdb -op X86_ANY
Method 2: AppHelp API
Alternatively, you can write the SDB using the Application Compatibility Database API functions exported from apphelp.dll
. Unfortunately, there isn't a public LIB for it, so linking against it would be difficult. You can call the functions to build the SDB with a script for my SprintDLL utility...
call apphelp.dll!SdbCreateDatabase /return native /into pdb (lpwstr "YourDatabase.sdb", int 0)
call apphelp.dll!SdbBeginWriteListTag /return int /into tDatabase (slotdata pdb, int 0x7001)
call apphelp.dll!SdbWriteDWORDTag (slotdata pdb, int 0x4023, int 1)
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x6001, lpwstr "Custom Database")
call apphelp.dll!SdbWriteBinaryTag (slotdata pdb, int 0x9007, blockptr(guid {5FB8C914-168C-4B9B-8256-DF8A0F384E3E}), int 0x10)
call apphelp.dll!SdbWriteQWORDTag (slotdata pdb, int 0x5001, long 0)
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x6022, lpwstr "3.0.0.9")
call apphelp.dll!SdbWriteDWORDTag (slotdata pdb, int 0x4021, int 37)
call apphelp.dll!SdbWriteDWORDTag (slotdata pdb, int 0x4055, int 0)
call apphelp.dll!SdbBeginWriteListTag /return int /into tLibrary (slotdata pdb, int 0x7002)
call apphelp.dll!SdbBeginWriteListTag /return int /into tShim (slotdata pdb, int 0x7004)
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x6001, lpwstr "TSLie")
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x600A, lpwstr "AcRes.dll")
call apphelp.dll!SdbWriteBinaryTag (slotdata pdb, int 0x9010, blockptr(guid {77F84F43-0675-4B79-99EE-E70E0CE45BFC}), int 0x10)
call apphelp.dll!SdbWriteDWORDTag (slotdata pdb, int 0x4021, int 37)
call apphelp.dll!SdbEndWriteListTag (slotdata pdb, slotdata tShim)
call apphelp.dll!SdbEndWriteListTag (slotdata pdb, slotdata tLibrary)
call apphelp.dll!SdbBeginWriteListTag /return int /into tExe (slotdata pdb, int 0x7007)
// TODO: Change EXE attributes appropriately
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x6001, lpwstr "SomeApp.exe")
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x6006, lpwstr "Some App")
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x6005, lpwstr "Some Vendor")
call apphelp.dll!SdbWriteBinaryTag (slotdata pdb, int 0x9004, blockptr(guid {BB751D03-9792-4208-886A-AFDBC5AA0EBE}), int 0x10)
call apphelp.dll!SdbWriteBinaryTag (slotdata pdb, int 0x9011, blockptr(guid {59CCFEAA-0A4B-4578-8B20-261A48D19E16}), int 0x10)
call apphelp.dll!SdbBeginWriteListTag /return int /into tMatching (slotdata pdb, int 0x7008)
// TODO: Change or add matching information as needed
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x6001, lpwstr "*")
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x6010, lpwstr "Some Product")
call apphelp.dll!SdbEndWriteListTag (slotdata pdb, slotdata tMatching)
call apphelp.dll!SdbBeginWriteListTag /return int /into tShimref (slotdata pdb, int 0x7009)
call apphelp.dll!SdbWriteStringTag (slotdata pdb, int 0x6001, lpwstr "TSLie")
call apphelp.dll!SdbEndWriteListTag (slotdata pdb, slotdata tShimref)
call apphelp.dll!SdbEndWriteListTag (slotdata pdb, slotdata tExe)
call apphelp.dll!SdbEndWriteListTag (slotdata pdb, slotdata tDatabase)
call apphelp.dll!SdbCloseDatabaseWrite (slotdata pdb)
...that can be invoked like so:
sprintdll run YourScript.sprint
Installing the SDB
Finally, regardless of how you produce the SDB, you can install it with the standard sdbinst
utility:
sdbinst YourDatabase.sdb
You have to think of this from Raymond Chen's point of view. Imagine if it were possible for somebody other than Microsoft to write compatibility shims. Then whenever Microsoft makes a breaking change, in addition to all their other compatibility work they will also have to write shims for the 3rd party shims that did the wrong thing. Maintaining backward compatibility is hard enough as it is.