My research into DWM animations (UxTheme AMAP)
Sept 3, 2023 22:11:21 GMT -8
Post by ephemeralViolette on Sept 3, 2023 22:11:21 GMT -8
Recently, I decided to research the animation map (AMAP) of UxTheme Visual Styles, which are used since Windows 8 for various animations across the operating system, including the window transition animations used by DWM. This is generally unexplored territory, as no publicly-released msstyles editor that I am aware of even really acknowledges that it is there. If you load a Windows 8+ msstyles into msstyleEditor, for example, you will only see blank classes named "animation" and "timingfunction". Obviously, these aren't particularly useful...
One person who did research this, on the other hand, is Lucas Brooks over on BetaWiki, who is developing ThemeTool. They have previously researched parsing the format for their own msstyles editor tool, but the tool is not currently public, which is why I decided to look into it myself. I owe a lot of credit to Lucas; I did steal their name documentation in preparation to publish my parser tool, which previously didn't have any user-friendly names to look for.
Oddly enough, the APIs for theme animations are public (although the only consumer I know about is DWM), but the documentation is barren.
Let's cut to the chase...
If you were expecting me to discover a way to restore the Windows 8 animations on Windows 10 or anything like that, sorry to disappoint, but it isn't going to work out. That's the entire reason I researched this format in the first place, if I'll be honest. The DWM user environment (uDWM.dll) in Windows 10, which implements the "CTopLevelWindow3D" class responsible for the window transition animations, does not account for 3D transforms in the animations at all, and instead, uniquely accounts for a 2D scale transform. As such, if you were to copy the Windows 8 animations to Windows 10, the animations wouldn't work correctly. The only thing that continues to be supported the same way is the opacity change, which means that the Windows 8 animation on 10 would only fade in or out. Here is a video demonstrating this behaviour.
You might be wondering why Microsoft went out of their way to store the animation data in the theme if the behaviour would be hardcoded in uDWM anyway; I don't get it either. If anything, it only has the potential to complicate things. From what I can tell, DWM doesn't even change the animation data when you change your theme, so you must restart DWM entirely to get it to update. What's even more strange is that the animations are hardcoded in Windows Vista and 7, so they specifically made this change for 8, even though it is practically pointless.
Fortunately, it seems almost too easy to modify DWM to port these animations back. I will need to analyse this more in the future, but I believe the loader in uDWM to retrieve animation data from UxTheme is a pretty consistent format between both Windows 8 and 10. As for how Microsoft wrote it, it is most likely a switch statement, and the cases for 3D transform were likely just commented out during Windows 10 development.
On the other hand, various leftovers from Windows 8 remain as late as the latest 10 releases (19041-19045), such as animation data for the charms bar and what I assume is the start screen ("Launcher").
Finally, it seems that Windows 11 added a new ID for the window transition animations, as the IDs that 10 and prior used remain in the file completely unmodified. This remains the case in the latest build I looked at: 22621.
The parser:
The parser tool I wrote is a bit of a mess, admittedly. It's something I quickly threw together for my own purposes, so it doesn't exactly output pretty data. I also did not write an encoder tool, so you are expected to understand the format anyway and to edit it using a hex editor.
I wrote the parser in PHP because it was the nicest language I could think of for hacking together a quick parser. My original plan was to hack together a parser in PHP, get down the format to a confident degree, and then write an easier-to-use, more powerful parser in a language like C#. But, as you can tell, this never came to fruition. Since I wanted to upload the PHP file at some point in a GitHub gist, I left it in one file, but this does mean it gets a little messy.
The format:
Part 1: Visual Style Records
From here, I will go into significantly more technical detail than I have been in the past. I will assume you are an absolute beginner to this, so let me explain how Visual Styles work exactly.
When Windows XP introduced the Visual Styles format, it already used version 3 (I guess 1-2 got swallowed up during Whistler development), and this stored all data about the style in a Portable Executable (PE) with embedded INI files. Although Visual Styles are PE files, they use the unique "msstyles" extension, and do not contain any executable code. Apart from storing bitmap images for the theme, it defines metadata in these INI files which describe classes, parts, states, etc.
Windows Vista changed the format of Visual Styles to embed some custom data formats instead of INI for information, which makes it quicker to parse the file, among other benefits. This marked the transition to version 4, and is an important part of almost all data storage in the msstyles file. Notably, there is a common header used for data definitions, known as simply the "Visual Style Record" header (internally called "_VSRECORD"). This goes before all data definitions and describes the most crucial information about the definition.
Here is a reconstructed C header for this structure:
typedef struct _VSRECORD
{
DWORD dwNameId;
DWORD dwTypeId;
DWORD dwClassId;
DWORD dwPartId;
DWORD dwStateId;
DWORD dwShortFlag;
DWORD dwReserved;
DWORD dwSizeInBytes;
} VSRECORD, *PVSRECORD;
When this data is stored in a file, the members of this structure will get stored sequentially. Numbers are stored in little-endian format, so 258 == "02 01 00 00" and not "00 00 01 02".
00000000 | 84 4E 00 00 F2 00 00 00 47 01 00 00 01 00 00 00 | „N..ò...G.......
00000010 | 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 | ................
Since the types here are DWORDs (32-bit unsigned integers), every 4 bytes refers to another property of the header. For example, "84 4E 00 00" refers to dwNameId, "F2 00 00 00" to dwTypeId, and so on... (I originally tried to colour code this information, but the forum editor won't let me.)
Documentation for this format can largely be found here in the msstyleEditor source code, which was my go to reference for the format. dwNameId and dwTypeId refer to the property name/type, which are described in MSDN. dwClassId points to a class ID, which is relative to the file; what would otherwise be the same exact data may differ in this value, as the class ID (which is an index) can be different. If dwSizeInBytes is set and dwShortFlag is not, then the record is followed by other data, rather than being standalone.
In the case of the animation map, there are some shortcuts that can be taken, as well as some other differences. From what I can tell, the animation map uses different names for what are ordinarily called "parts" and "states" by Visual Styles: "storyboards" and "targets", respectively. They otherwise function the exact same way, but knowing that can help when reverse engineering the functions used to access the animations specifically. Along with this, the short flag is never set, so there is always information appended to the animations. Finally, there are two unique names and class pointers that are used by every definition in the AMAP, for each of the two types:
- 20100 (timing functions)
- 20000 (animations)
Respectively, the class IDs will be the index of the classes named: "timingfunction" and "animations".
So, as you may have guessed, the AMAP stores two types of data: timing functions and animations. And you'd be correct! That's all there is to find in the AMAP. Timing functions come at the front of the data, so let's look at them more closely...
Part 2: AMAP Timing Functions
Well, if you were to go into the UxTheme header (UxTheme.h) file from the Windows SDK, unlike the animation structure, there are actually complete structures for the timing functions. This is titled "TA_TIMINGFUNCTION", which has only one type: "TA_CUBIC_BEZIER":
typedef enum TA_TIMINGFUNCTION_TYPE
{
TTFT_UNDEFINED,
TTFT_CUBIC_BEZIER,
} TA_TIMINGFUNCTION_TYPE;
typedef struct TA_TIMINGFUNCTION
{
TA_TIMINGFUNCTION_TYPE eTimingFunctionType;
} TA_TIMINGFUNCTION, *PTA_TIMINGFUNCTION;
typedef struct TA_CUBIC_BEZIER
{
TA_TIMINGFUNCTION header;
float rX0;
float rY0;
float rX1;
float rY1;
} TA_CUBIC_BEZIER, *PTA_CUBIC_BEZIER;
For all intents and purposes, since this is the only type that currently exists, you can cheat in parsing it by just parsing the cubic bezier type. Note that the header member basically just stands for all of the members of the preceding type name, so in this case, it just copies eTimingFunctionType from the TA_TIMINGFUNCTION structure. This is the approach I used in my naive parser written in PHP, but that's not to say it's a good idea to assume Microsoft will always store well-formed data.
If you were to create a non-naive reader of this data type, however, you would only read one 32-bit integer into the data, validate the type, and then continue to read the remaining length respective of the type enumeration (that first integer you read).
As mentioned previously, this data would go immediately after a Visual Style Record header. Finally, it seems to always have 8 bytes of padding between it and the next record. In this case, the padding bytes will always have a value of 0.
Part 3: AMAP Animation Definitions
The animation definitions are by far the most complicated part of this all. Although there are definitions available in the aforementioned header file, these are only applicable to child data, specifically the transform array. There is another header used that follows the Visual Style record one, followed by a properties list. These, as far as I can tell, do not have public header information, and I spent several hours figuring out how they worked on my own.
The only reason why the transform structures are even public is because they are used as output by the GetThemeAnimationTransform function, which is publicly exported for some reason.
To save you the burden, here are the missing headers that I reconstructed for the data types:
/* Common theme animation (TA) header: */
typedef struct _TA_HEADER
{
DWORD dwSizeInBytes;
DWORD dwPropertiesIndex;
DWORD dwTransformsIndex;
} TA_HEADER, *PTA_HEADER;
/* Common theme animation properties structure. */
typedef struct _TA_PROPERTIES
{
DWORD dwAnimationFlags;
DWORD dwTransformCount;
DWORD dwStaggerDelay;
DWORD dwStaggerDelayCap;
float rStaggerDelayFactor;
long zOrder;
DWORD dwBackgroundPartId;
DWORD dwTuningLevel;
float rPerspective;
} TA_PROPERTIES, *PTA_PROPERTIES;
/* Undocumented 3D transform types */
enum TA_TRANSFORM_TYPE_3D
{
TATT_TRANSLATE_3D = 258,
TATT_SCALE_3D = 259,
TATT_ROTATE_3D = 260
};
/* Undocumented 3D transform structure, used by all above types. */
typedef struct _TA_TRANSFORM_3D
{
TA_TRANSFORM header;
float rX;
float rY;
float rZ;
float rInitialX;
float rInitialY;
float rInitialZ;
float rOriginX;
float rOriginY;
float rOriginZ;
} TA_TRANSFORM_3D, *PTA_TRANSFORM_3D;
Whew. That's a lot to take in.
When describing a theme animation rather than a timing function, the data following the Visual Style Record will be a TA_HEADER (which is my name, by the way, not Microsoft's). This stores another copy of the size in bytes, which will be the exact same as that of the VS Record, and two pointers, pointing respectively to the indices of the properties structure and the first transform (or the end of the properties header? In the case where there are no children, it will be the end of the properties header).
Following the header will be another 4 bytes of padding. It seems that Microsoft didn't even care to zero this out prior to Windows 10, since the AMAP from Windows 8.1 and prior show what seem to be random memory contents from their build machines in the gaps (that is, snippets of what appears to be English text).
Following the header and padding will be a TA_PROPERTIES structure (again, my own name) which will declare global metadata for the entire animation. Notably, this is responsible for declaring how many transforms are used, which is vital to the next step in parsing this data. The properties can be accessed through the publicly exported API: GetThemeAnimationProperty. This is again padded with 4 bytes in the same way as the header.
Finally, an array of transforms (that should be equal in count to the specified count in the properties) follows the properties and its padding. These are definitions of the public transform types (or the private 3D transform one) and vary in size. Each transform definition contains padding, which varies in length depending on its size. Here is the algorithm I use for calculating the size of the padding for these transforms:
int GetPaddingForSize(int size)
{
const int sizeOfRecordHeader = 32;
return ((size + 39) & -8) - sizeOfRecordHeader - size;
}
The only transform types that I am aware are:
- 2D transform (TA_TRANSFORM_2D) (public)
- 2D translate (TATT_TRANSLATE_2D) (public)
- 2D scale (TATT_SCALE_2D) (public)
- 2D translate (TATT_TRANSLATE_2D) (public)
- Opacity (TA_TRANSFORM_OPACITY) (public)
- Clip (TA_TRANSFORM_CLIP) (public)
- 3D transform (TA_TRANSFORM_3D)
- 3D translate (TATT_TRANSLATE_3D)
- 3D scale (TATT_SCALE_3D)
- 3D rotate (TATT_ROTATE_3D)
I am not sure really any of the animation properties mean (apart from the transform count), but the transforms themselves are quite easy to understand. Their common header is used to script timings, and then their unique properties are quite intuitive as well. These took me almost two days straight to figure out, so I am sorry that I don't have much more to say. It feels like I haven't written quite enough on them yet, but I think that may just be an illusion given how much time I've spent looking into these.
Part 4: Other things about the format
I presume that most consumers of this data convert it to their own format before using it. DWM does this at least.
All pointers in the format are relative or point to external data in some way, which means you can easily insert or remove data from the AMAP in a hex editor. The most you will have to keep track of is changing the length properties in the data's own headers.
Future things and closing:
As mentioned, more work needs to be done in order to restore the 3D window transitions from Windows 8 back to Windows 10. I plan to look into this further in the future, and if I don't, I have forwarded valuable information about DWM's handling of the animations to Dulappy and he may reimplement the Windows 8 handlers. That was the plan all along.
As I don't see this as particularly valuable to continue, I will probably not work on this project much anymore. I think that it is finished the way it is. If anyone else wishes to make a better tool and use my source code as a reference, please go ahead.