AniMe Matrix GA402R

Hello, world!

Been a long time since I've had anything worthwile to write about, but it finally happened. In the recent days I've become a happy owner of that ASUS ROG Zephyrus G14 2022 laptop - I instantly fell in love with its AniMe Matrix feature, so naturally the first thing I did was reverse engineer the living fuck out of it. So far the findings are promising. I will be updating this page with more information as I discover more things about it.

Keep in mind I have no clue what I'm doing. Should you choose to experiment with your own device, feel free to go ahead and take anything you think might help you. The only thing I ask for in exchange is you linking back to my GitHub if anything in here turns out to be useful in whatever you're trying to do.


How the heck do I even talk to you?

First things first, you cannot tell a machine to do things if you don't know how to tell it to do things, right? It turns out the entire AniMe Matrix module is a USB HID device. With VID:PID pair of 0B05:193B one can identify it and then utilize a connection to send/receive HID reports to/from it. Make sure to uninstall/deactivate the Aura Service if you're on Windows - rather unsurprisingly it spams the fuck out of the USB port the LED matrix is connected to.

What the heck do I tell you now?

USB HID uses a concept of "reports" which are essentially packets of data you can exchange with a HID-compliant USB device. The reports have a special byte identifying them, called "Report ID" reports also have a specific maximum output report length, which is contained in the device's HID descriptor. In case of AniMe Matrix it's exactly 640 bytes. I figured out the correct report ID by seeing what "Armoury Crate" companion application does -- the correct report ID is 0x5E. I've also brute-forced every report ID with GET_REPORT requests, the only correct ID seems to be the one I've mentioned in the last sentence, and the meaningful data returned from it was the following 32-bytes (the rest of the 640 bytes was just zeroes).

5E C2 81 00 00 00 00 00 00 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00 01 00 00 00 9C 07 08 00

Cryptic. Or is it? Maybe. I have a good idea what the first 16 bytes mean, but completely no clue what the last 16 bytes mean. I'll update this section once I have a clear picture of what is what.

Getting the data from the device

At the time of writing this section there's no known way of retrieving the current internal panel state. I will keep playing around with it and see if it's possible to retrieve the data AniMe Matrix is stored, but right now it appears to me the device is write-only.

Known commands

Numbers in headers are hexadecimals unless otherwise stated.

C0 02 XX XX YY YY ... - set pixel state register buffer

This, along with the next one are probably the most interesting commands in this list. This single command lets you draw on the LED matrix. It takes 2 16-bit little-endian integers and then an array of bytes describing the desired states of the pixels:

XX XX: 1-based linear address of the LED at which to start updating the buffer. If the laptop is facing its screen towards you and then you close the lid, then the LED at address 1 is the bottom-right and increments going leftwards, then wraps around to the start, and so on.

YY YY: The amount of LEDs to update. The maximum amount you can specify here is 0x0278 - seems to be the hard limit of the device registers. Armoury Crate updates 0x0273 LEDs at once.

...: Byte array representing brightness of each LED you declared you're going to update - I'm not sure about the 2021 range of AniMe Matrix-equipped laptops, but the grayscale of the 2022 model is pretty damn good. The individual brightness increases visually in steps of more or less 3 values, so 0x00-0x03 is effectively off, 0x04 is the lowest light level you'll get on the "full" (see C0 04) global panel brightness level.

C0 03 - flush internal display registers

This command does nothing on its own. Used in conjunction with C0 02 to update the pixel states. If you neglect to issue this command you simply won't observe any changes to your AniMe Matrix.

C0 04 XX - set global panel brightness

This command takes 1-byte parameter and the only recognized bits are the 2 least-significant ones. The LED matrix supports 4 different brightness modes:
00 - Panel is off.
01 - Minimum brightness.
02 - Medium brightness.
03 - Maximum brightness.
This setting acts as an overall multiplier of individual LED states, so 0xFF for a single pixel will vary depending on what the value of this register is set to.

C3 01 XX - panel global state toggle

8-bit parameter and seems to only recognize the most-significant bit of the parameter byte:
00 - Enable the LED matrix.
80 - Disable the LED matrix.

C4 01 XX - built-in animation mode toggle

8-bit parameter and seems to only recognize the most-significant bit of the parameter byte:
00 - Enable the built-in animation mode.
80 - Disable the built-in animation mode.

C5 XX - configure built-in animation

AniMe Matrix has 2 built-in animations for every supported state that it can play on its own, making up a total of 8 animation to pick and choose from. Which animation is used is configured by the 4 least-significant bits of the parameter byte:

| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0: built-in normal (running state) animation:
    : 0 = use built-in normal animation A (ROG logo and binary numbers banner scroll)
    : 1 = use built-in normal animation B (Glitched ROG logo)

1: built-in sleep animation:
    : 0 = use built-in sleep animation A (ROG logo banner swipe)
    : 1 = use built-in sleep animation B (Meteor shower)

2: built-in shut-down animation:
    : 0 = use built-in shut-down animation A (ROG logo glitch-out) 
    : 1 = use built-in shut-down animation B (See-ya text)

3: built-in boot-up animation:
    : 0 = use built-in boot-up animation A (ROG logo glitched construction)
    : 1 = use built-in boot-up animation B (ROG logo emerging from static)