Skip to content

Instantly share code, notes, and snippets.

@khang06
Last active August 3, 2024 16:07
Show Gist options
  • Save khang06/6186543b560548370ce7cc08cad7f710 to your computer and use it in GitHub Desktop.
Save khang06/6186543b560548370ce7cc08cad7f710 to your computer and use it in GitHub Desktop.
O3C Internals (WIP)

These are some fairly unorganized notes from reverse engineering the SayoDevice O3C. I doubt this information would be useful for most people, but it's better to put it out there than to leave it publicly undocumented.

Useful Links

CH32FV2x_V3x Reference Manual

CH32V307 SDK and samples

MounRiver Studio (toolchain for the CH32)

SayoDeviceStreamingAssistant

Firmware Updates

The latest firmware update can be found at https://a.sayobot.cn/firmware/update/9/firmware/app_O3C.bin.

The firmware image is encrypted with AES-256-CBC with the key C4053DDF225E89F74868C1E1F4C00D514F02A8A8692F997869ABEB155250150C and a zero IV. After decryption, it's written to flash and is accessible at address 0x4000. The decrypted image can be directly loaded into IDA/Ghidra/whatever at that offset.

Some metadata is stored at 0x29F80 in the firmware image. On every boot and right before rebooting after a firmware update, the bootloader performs an integrity check on the firmware. The size of the image is stored at 0x29F84 and a MD5 hash is stored at 0x29FA0. If the hash doesn't match, the device will either force itself into bootloader mode or refuse to reboot after the update.

Bootloader

A dump of the bootloader (along with an old firmware) can be found here. This gets loaded at 0x0.

USB HID (API v1)

To configure the device, custom USB HID commands are sent. The device has a VID of 0x8089, a PID of 0x0009, and a usage page of 0xFF00.

Commands and responses follow the following format:

struct hid_packet_v1_t {
    uint8_t report_id;      // 0x00, always 2
    uint8_t id;             // 0x01
    uint8_t length;         // 0x02
    uint8_t data[length];   // 0x03
    uint8_t checksum;       // length + 0x03, sum of all previous bytes
    // padded to 64 bytes
};

Command names are taken from an old version of the web UI's code.

0x00 - MetaInfo

TODO

0x01 - MemoryRead

TODO

0x02 - MemoryWrite

TODO

0x04 - Save

TODO

0x06 - SimpleKey

TODO

0x08 - DeviceName

TODO

0x0B - Password

TODO

0x0C - Text

TODO

0x10 - Light

TODO

0x11 - Palette

TODO

0x16 - Key

TODO

0x31 - ScreenStart

TODO

0x32 - ScreenMain

TODO

0x33 - ScreenSleep

TODO

0xFC - Option

TODO

0xFF - Bootloader

TODO

USB HID (API v2)

The device for API v2 also has a VID of 0x8089 and a PID of 0x0009, but the usage page is 0xFF12 for high-speed mode (8000hz) and 0xFF11 for the other polling rates.

Also, the commands follow a different format:

struct hid_cmd_v2_t {
    uint16_t length;            // 0x00
    uint8_t id;                 // 0x02
    uint8_t index;              // 0x03, used for identifying which response corresponds to a command within a packet
    uint8_t data[length - 4];   // 0x04
    // padded to a multiple of 4 bytes
};

struct hid_packet_v2_t {
    uint8_t report_id;      // 0x00, 0x22 for high-speed mode, 0x21 otherwise
    uint8_t echo;           // 0x01, always 3 in the web UI, doesn't seem to get read?
    uint16_t checksum;      // 0x02, sum of struct reinterpreted as 16-bit words
    hid_cmd_v2_t cmds[];    // 0x04
    // padded to 1024 bytes for high-speed mode, 64 bytes otherwise
};

Command names are taken from the web UI's code.

0x00 - Info

struct info_res_t {
    uint16_t model_code;        // 0x00
    uint16_t firmware_version;  // 0x02
    uint8_t unk4[4];            // 0x04
    uint8_t battery;            // 0x08, hardcoded to 0
    uint8_t fn;                 // 0x09
    uint8_t cpu_s;              // 0x0A
    uint8_t cpu_ms;             // 0x0B
    uint8_t unkC[0x17];         // 0x0C
};

0x01 - DeviceName

// Can also be sent to set name
struct info_res_t {
    uint32_t name[12];  // 0x00, unicode string
};

0x02 - SysInfo

struct sys_req_t {
    uint16_t width;         // 0x00
    uint16_t height;        // 0x02
    uint8_t refresh_rate;   // 0x04
    // 1 byte of padding
    uint16_t sys_ms;        // 0x06
    uint32_t sys_s;         // 0x08
    uint16_t vid;           // 0x0C
    uint16_t pid;           // 0x0E
    uint8_t cpu_1m;         // 0x10
    uint8_t cpu_5m;         // 0x11
    // 2 bytes of padding
    uint32_t cpu_freq;      // 0x14
    uint32_t hclk;          // 0x18
    uint32_t pclk_1;        // 0x1C
    uint32_t pclk_2;        // 0x20
    uint32_t adc_0;         // 0x24
    uint32_t adc_1;         // 0x28
};

0x03 - Setting

TODO

0x04 - BLE

Referenced in the web UI, but not implemented on the O3C.

0x05 - DeviceLock

TODO

0x06 - DevoceUnlock [sic]

TODO

0x07 - IO

Referenced in the web UI, but not implemented on the O3C.

0x08 - MonkeyIO

Referenced in the web UI, but not implemented on the O3C.

0x09 - MonkeyKey

Referenced in the web UI, but not implemented on the O3C.

0x0D - Save

TODO

0x0E - SysControl

TODO

0x10 - Key

TODO

0x11 - Light

TODO

0x12 - Palette

TODO

0x13 - Touch

Referenced in the web UI, but not implemented on the O3C.

0x14 - MagneticTrigger

TODO

0x15 - MagneticDepth

TODO

0x16 - Password

TODO

0x17 - Text_GBK_ASCII

TODO

0x18 - Text_U16

TODO

0x19 - ScriptPreview

TODO

0x1A - ScriptStep

TODO

0x1E - KeyStatu [sic]

TODO

0x1F - KeyData

TODO

0x20 - Image

TODO

0x21 - ScreenStart

TODO

0x22 - ScreenMain

TODO

0x23 - ScreenSleep

TODO

0x25 - Display

Dumps the keypad's framebuffer.

struct display_req_t {
    uint32_t byte_offset;       // 0x00
};

struct display_res_t {
    uint32_t byte_offset;           // 0x00
    uint16_t framebuffer[0x1FA];    // 0x04, 0x1A elements on non-highspeed
};

0xFF - Broadcast

Referenced in the web UI, but not implemented on the O3C.

Screen Layers

The O3C lets the user customize 3 screens: main, sleep, and startup. Each screen is made up of 16 layers, each of which can be swapped out and configured.

Layer type names are taken from the web UI's code.

0x00 - Null

Does nothing.

0x01 - Color

Fills a rectangle with a configurable color, size, and position.

0x02 - Widget

Displays a widget.

NOTE: IDs are off by 1 in the web UI!

0 - Null

Does nothing.

1 - Vertical Key Pressure

2 - Horizontal Key Pressure

3 - CPU Usage

4 - Bongocat

5 - Key Display

Can't be moved for some reason.

6 - Clock Debug

idk

0x03 - Counter

Displays a key counter.

0x04 - ASCII

Displays some text.

0x06 - Icon

Displays a hardcoded image.

NOTE: IDs are off by 1 in the web UI!

0 - Random Anime Girl

1 - Nyan Cat

(TODO: Get a GIF of this)

0x07 - Picture

Displays a user-uploaded image.

(TODO: Reverse engineer the format)

0xFF - Clear

Clears the entire screen with a configurable color.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment