No backup, maybe mercy: restoring a Ticwatch C2+ to WearOS from AsteroidOS

The Ticwatch C2+, although an unpopular model compared to its regular C2 sibling, is one of the most elegant smartwatches I know of today that is still somewhat available on the market: it is not “sporty” and not that thick, but best of all, it is affordable and easily moddable thanks to the exposed USB connection with the charging dock.

While the only characteristic that sets aside the C2+ from the C2 is the doubled amount of RAM, because of its unpopularity, there is currently no available firmware online that restores the C2+ to its stock WearOS system. But there is no need to panic, as thanks to the kindness of beroset from AsteroidOS, we finally got a system dump on the Internet!

This is a little guide on how to flash the aforementioned system dump if you want to go from AsteroidOS to WearOS on your skipjack-plus, in case you did not dump the WearOS image beforehand.

Before you dig into this: if you mangled the eMMC's GPT or partitions that are not the following:

Then your chances of recovering the watch with the hereby provided image and guide drop significantly. Before announcing your deluxe paperweight to the world, remember to proceed with caution!

Obtaining and prepping the image

First step is to obviously download the image that you can find here. Extract the file to obtain the raw disk image, hovering around 4GB in size.

The file you have obtained is currently not mountable, as it contains all the partitions available on the eMMC. According to kpartx, we have about 37 of them:

$ sudo kpartx original-skipjack.img
loop0p1 : 0 131072 /dev/loop0 32768
loop0p2 : 0 64 /dev/loop0 163840
loop0p3 : 0 1024 /dev/loop0 163904
loop0p4 : 0 1024 /dev/loop0 164928
loop0p5 : 0 2048 /dev/loop0 165952
loop0p6 : 0 2048 /dev/loop0 168000
loop0p7 : 0 1024 /dev/loop0 170048
loop0p8 : 0 1024 /dev/loop0 171072
loop0p9 : 0 1536 /dev/loop0 172096
loop0p10 : 0 1536 /dev/loop0 173632
loop0p11 : 0 2048 /dev/loop0 175168
loop0p12 : 0 3072 /dev/loop0 177216
loop0p13 : 0 3072 /dev/loop0 180288
loop0p14 : 0 2048 /dev/loop0 183360
loop0p15 : 0 3072 /dev/loop0 185408
loop0p16 : 0 512 /dev/loop0 188480
loop0p17 : 0 512 /dev/loop0 188992
loop0p18 : 0 512 /dev/loop0 189504
loop0p19 : 0 512 /dev/loop0 190016
loop0p20 : 0 32 /dev/loop0 190528
loop0p21 : 0 16 /dev/loop0 190560
loop0p22 : 0 2 /dev/loop0 190576
loop0p23 : 0 2 /dev/loop0 190578
loop0p24 : 0 65536 /dev/loop0 196608
loop0p25 : 0 65536 /dev/loop0 262144
loop0p26 : 0 2048 /dev/loop0 327680
loop0p27 : 0 1024 /dev/loop0 329728
loop0p28 : 0 32768 /dev/loop0 330752
loop0p29 : 0 1024 /dev/loop0 363520
loop0p30 : 0 20480 /dev/loop0 364544
loop0p31 : 0 131072 /dev/loop0 393216
loop0p32 : 0 65536 /dev/loop0 524288
loop0p33 : 0 65536 /dev/loop0 589824
loop0p34 : 0 2621440 /dev/loop0 655360
loop0p35 : 0 524288 /dev/loop0 3276800
loop0p36 : 0 32768 /dev/loop0 3801088
loop0p37 : 0 3637215 /dev/loop0 3833856

Therefore, to do anything with the the file, you will need to render the individual partitions available to the machine. This can be easily done with:

$ sudo kpartx -a original-skipjack.img

All the partitions should now be accessible under /dev/mapper and to confirm this, we will check lsblk:

$ lsblk 
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0         7:0    0   3.6G  0 loop 
├─loop0p1   253:0    0    64M  0 part 
├─loop0p2   253:1    0    32K  0 part 
├─loop0p3   253:2    0   512K  0 part 
├─loop0p4   253:3    0   512K  0 part 
├─loop0p5   253:4    0     1M  0 part 
├─loop0p6   253:5    0     1M  0 part 
├─loop0p7   253:6    0   512K  0 part 
├─loop0p8   253:7    0   512K  0 part 
├─loop0p9   253:8    0   768K  0 part 
├─loop0p10  253:9    0   768K  0 part 
├─loop0p11  253:10   0     1M  0 part 
├─loop0p12  253:11   0   1.5M  0 part 
├─loop0p13  253:12   0   1.5M  0 part 
├─loop0p14  253:13   0     1M  0 part 
├─loop0p15  253:14   0   1.5M  0 part 
├─loop0p16  253:15   0   256K  0 part 
├─loop0p17  253:16   0   256K  0 part 
├─loop0p18  253:17   0   256K  0 part 
├─loop0p19  253:18   0   256K  0 part 
├─loop0p20  253:19   0    16K  0 part 
├─loop0p21  253:20   0     8K  0 part 
├─loop0p22  253:21   0     1K  0 part 
├─loop0p23  253:22   0     1K  0 part 
├─loop0p24  253:23   0    32M  0 part 
├─loop0p25  253:24   0    32M  0 part 
├─loop0p26  253:25   0     1M  0 part 
├─loop0p27  253:26   0   512K  0 part 
├─loop0p28  253:27   0    16M  0 part 
├─loop0p29  253:28   0   512K  0 part 
├─loop0p30  253:29   0    10M  0 part 
├─loop0p31  253:30   0    64M  0 part 
├─loop0p32  253:31   0    32M  0 part 
├─loop0p33  253:32   0    32M  0 part 
├─loop0p34  253:33   0   1.3G  0 part
├─loop0p35  253:34   0   256M  0 part 
├─loop0p36  253:35   0    16M  0 part 
└─loop0p37  253:36   0   1.7G  0 part 

Getting to know the watch's eMMC

Now that the partitions are individually accessible, we could mount and read them if we wanted to, but instead we will need to understand which partition is which. That is, where are the partitions mounted on the watch.

As you already know, partitions on Android devices have names (e.g. vendor_a or boot) and the same applies to WearOS: on this specific Qualcomm platform (msm8909w), the eMMC exposes the partition symlinks we need at /dev/block/platform/msm_sdcc.1/by-name/ thanks to init.rc.

Now take your Asteroid-powered watch and, if it is not already on by default, enable SSH mode (or developer mode, depending on your language) in Asteroid's settings app under the USB menu and SSH into your device with:

$ ssh root@192.168.2.15

Note that the charging dock for the C2s is fairly finicky when it comes to making contact with the watch: in case you are not getting a stable connection with it, try readjusting the watch on the dock, use an USB 2.0 port or reboot your computer altogether.

Once inside the device, check for the partition names under the eMMC directory we mentioned earlier:

root@skipjack:~# ls -las /dev/block/platform/msm_sdcc.1/by-name/
     0 drwxr-xr-x    2 root     root           780 Apr 28 17:42 .
     0 drwxr-xr-x    3 root     root            60 Apr 28 17:42 ..
     0 lrwxrwxrwx    1 root     root            21 Apr 28 17:42 DDR -> ../../../../mmcblk0p2
     0 lrwxrwxrwx    1 root     root            21 Apr 28 17:42 aboot -> ../../../../mmcblk0p5
     0 lrwxrwxrwx    1 root     root            21 Apr 28 17:42 abootbak -> ../../../../mmcblk0p6
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 boot -> ../../../../mmcblk0p33
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 cache -> ../../../../mmcblk0p31
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 cmnlib -> ../../../../mmcblk0p16
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 cmnlibbak -> ../../../../mmcblk0p17
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 config -> ../../../../mmcblk0p29
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 devinfo -> ../../../../mmcblk0p26
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 fsc -> ../../../../mmcblk0p22
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 fsg -> ../../../../mmcblk0p15
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 keymaster -> ../../../../mmcblk0p18
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 keymasterbak -> ../../../../mmcblk0p19
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 keystore -> ../../../../mmcblk0p27
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 metadata -> ../../../../mmcblk0p36
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 misc -> ../../../../mmcblk0p14
     0 lrwxrwxrwx    1 root     root            21 Apr 28 17:42 modem -> ../../../../mmcblk0p1
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 modemst1 -> ../../../../mmcblk0p12
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 modemst2 -> ../../../../mmcblk0p13
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 oem -> ../../../../mmcblk0p28
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 pad -> ../../../../mmcblk0p11
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 persist -> ../../../../mmcblk0p24
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 persistbak -> ../../../../mmcblk0p25
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 recovery -> ../../../../mmcblk0p32
     0 lrwxrwxrwx    1 root     root            21 Apr 28 17:42 rpm -> ../../../../mmcblk0p7
     0 lrwxrwxrwx    1 root     root            21 Apr 28 17:42 rpmbak -> ../../../../mmcblk0p8
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 rsv -> ../../../../mmcblk0p23
     0 lrwxrwxrwx    1 root     root            21 Apr 28 17:42 sbl1 -> ../../../../mmcblk0p3
     0 lrwxrwxrwx    1 root     root            21 Apr 28 17:42 sbl1bak -> ../../../../mmcblk0p4
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 sec -> ../../../../mmcblk0p20
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 splash -> ../../../../mmcblk0p30
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 ssd -> ../../../../mmcblk0p21
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 system -> ../../../../mmcblk0p34
     0 lrwxrwxrwx    1 root     root            21 Apr 28 17:42 tz -> ../../../../mmcblk0p9
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 tzbak -> ../../../../mmcblk0p10
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 userdata -> ../../../../mmcblk0p37
     0 lrwxrwxrwx    1 root     root            22 Apr 28 17:42 vendor -> ../../../../mmcblk0p35

Flashing the partitions

Now that we know which partition name corresponds to what partition number, we must get the partitions we mounted on our computer to the watch. Here are the three relevant partitions and their respective partition numbers needed to recover the watch:

/dev/mapper/loop0p33 -> boot
/dev/mapper/loop0p34 -> system
/dev/mapper/loop0p35 -> vendor

Because the C2 only offers about 1.74G of storage (around 50% of which already occupied by the OS), it would not be possible to copy our images over on the watch and flash them with dd.

To get around this limitation, we are forced to flash the images through ssh:

$ dd if=/dev/mapper/loop0p33 | ssh root@192.168.2.15 dd of=/dev/block/mmcblk0p33
$ dd if=/dev/mapper/loop0p34 | ssh root@192.168.2.15 dd of=/dev/block/mmcblk0p34
$ dd if=/dev/mapper/loop0p35 | ssh root@192.168.2.15 dd of=/dev/block/mmcblk0p35

Because AsteroidOS flashes itself on the userdata partition similarly to other hybris-powered systems, the system will still functional, so you can kindly ask it to go into fastboot mode through Settings -> Reboot -> Bootloader.

Now that you are in bootloader mode, do:

$ sudo fastboot erase userdata
$ sudo fastboot erase cache

And reboot the watch by long-pressing the power button while the selector is on “START”.

Stuck at the spinning colored dots

If after 20 minutes your device still didn't boot, try the following:

  1. Hold the power button for several seconds until the watch reboots
  2. Right after the panel goes black, while keeping the power button held down, start rapidly pressing on the screen with one finger on the top left and another finger on the bottom right corner until you hear three vibrations
  3. You should now be in bootloader mode: select “Reboot into recovery mode” with the navigation methods provided
  4. Select the options “Wipe cache partition” and “Wipe data/factory reset” to factory reset your watch
  5. Reboot the system and you should be good to go!

Hopefully you are now in WearOS with a functioning watch and a lesson that was learned the hard way: always make backups!

P.S. Partly rewritten in 2024 to avoid having a stroke while reading it.