The keyedlimepie blog

voidanix's nonsense ramblings

If you have been keeping up with recent emulator news, you surely might know about the yuzu emulator being taken down by the big N company. The reasons for such takedown are, to this day, quite unclear due to the many gray areas traversed by emulators in general. The aftermath for the developers also has remained a mystery.

What is clear is that the yuzu team has decided to keep their heads down and pay the lawsuit. This move will probably set them in debt for the next 20 years, yet I cannot blame them for choosing the path of least resistance with such a vexatious company like big N.

On top of the exorbitant payment requested, the team also had to take down their GPLv3 licensed code off of GitHub and replace their website with a message. This was probably requested by big N to demonstrate, in classic Japanese fashion, how they can bend anybody at their will if you ever have to deal with them.

Because the yuzu source code is open source and under a copyleft license, it is obvious to see how the project could not be killed that easily. Yet, forks could also die, and not by stabbing them with a DMCA.

Let us discuss a bit about the situation of suyu, currently one of the two forks attempting to improve upon the yuzu source code.

The faith of glass-made cutlery

At this point, yuzu forks started appearing out of the blue. Most of them claimed to have the latest checkout of the code. The most notable ones were yuzu-mirror, suyu, nuzu and, later on, sudachi.

yuzu-mirror was, from the beginning, never intended as a development platform for the Switch emulator, and has therefore kept a distance from new pull requests.

Some news outlets started picking up these repos and spun them as “official yuzu continuations”. The person behind nuzu was caught red-handed setting up a Patreon to accept money, regardless of their total lack of skill: this brought utter death and mistrust upon the fork, leading it to be blank shortly after. Having a Patreon for development was also speculated to be one of the core reasons why the lawsuit against yuzu was so effective.

Suyu, instead, was aiming at resolving the legal issues that have been found and weaponized against yuzu. ArsTechnica has a genuinely interesting article on how they would like to achieve that, which I would like to quote on the matter:

After consulting with an unnamed “someone with legal experience” (Sharpie would only say “they claimed three years of law school”), the Suyu development team has decided to avoid “any monetization,” Sharpie said.

Considering this unnamed person might be the only “legal” consultation the suyu team might have access to, I forecast some trouble down the road.

Reinforcing that fragile fork

At this point, suyu started its rebranding process: a Discord server was created, a new W.I.P. website appeared and folks started spamming their GitLab instance with README modifications and name substitutions. Thus far, no new code was added.

While the suyu people were busy modifying their README, a new fork named sudachi came along. The fork was made by an experienced developer for the Apple ecosystem, which also had an interest in emulators.

As of the day of writing, sudachi does not currently accept contributions, but has made some changes that will be found in suyu later on.

Let the crystalline rain commence

Now that there was seemingly a real suyu team and the infrastructure was set up, the repo started seeing a bunch of “contributions”. These were not only README edits or CI scripts additions, but also assets and “code”.

Talk is cheap.

The suyu people seem to have a grudge against clang-format. Other than that, it is very hard to see proper emulator code written and be properly reviewed to take advantage of it.

Let us take a look at the amount of commits against clang-format since yuzu was forked:

9858de7fceedece0715e Run clang-format
de83c5e6a6a1ca6cbb60 fix: clang format
18baf880c4ebd41bdd58 fix: clang format
3037f0b869aa129acb0a Fix clang-format error
2ceae9a0c121fe6f71e2 Nmajkic/clang fix
70d0df5e551aa087b088 fix: Clang fix part 2: Electric bogaloo
94a84f5943e68dce2f28 fix: CLang fix
c433758ed0083071f2a0 Run clang-format
b4163895330259a8d1fb Run clang-format
ab3e51e50d5cc45594ff fixes clang format error in !193
b6ad090424bd770e5e49 try to fix clang again
77e9b7b59bb31bf93e4d try 3 fixing clang omfg its just a space
fd1ec51496917ee656e9 try 2 fix clang
eb306775c616c4ba6db5 fix clong format

I have added an ellipsis, not only because I might have missed some, but also because I am sure they will keep going.

Instead, let us take a look at some proper code commits which might take advantage of all the clang-format modifications. We will analyze the two following ones to also understand suyu's coding standards:

  • 7215ac95437dd041fc40 ("Fix NROs crashing and loading infinitely")
  • 8755d2bad429c393d693 ("Require both keys to use the emulator")

Analyzing 7215ac95437dd041fc40

In the first commit, we observe... memset() being used on an std::vector to zero it out? What?

Let us check the author's justification for this code, which was rigorously not given in the commit message but can be found in the git instance's PR body:

nullequal says:
as the title says this fixes issues with NROs not loading at all

the real problem was that out_control was filled with useless junk of instead of being filled with zeros

Well, this does not justify the use of memset() and does not answer why the vector is supposed to be zeroed out.

On top of that, there seems to be a complete misunderstanding about the role that std::vector was supposed to have: std::vector<u8>& out_control is indeed supposed to be filled with “junk”, being just a buffer.

There are many alternative, safer ways to zero out a vector (or maybe even clear it), but this commit does not seem to look at the root of the issue: additionally, both reviewers approved the changes and one of them even complimented the commit author. This brings the number of people who likely did not understand the code to three.

Considering that the query “how to zero out a vector in c++” gives more reasonable, different results on any search engine, I highly suspect that the code has been suggested by some form of LLM, but I will leave that as an exercise to the reader.

Analyzing 8755d2bad429c393d693

This commit seems to aim at requiring both prod.keys and title.keys for the emulator to be usable.

The logic used in the commit's code is absurd: it only checks if both files are present in the required directories without validating the keys.

The most amusing part of this code is that the function it is in, KeyManager::BaseDeriveNecessary(), is called by ContentManager::AreKeysPresent(). Because the code returns true when these files are not found, it implies that the keys are actually present on the system.

Since the GitLab repository has been struck by a DMCA and knowing the author's commit history, it is very likely that this commit was pushed to the repo without being reviewed at all.

Show me the code. Yours.

The most astounding aspect about their entire development is the QA. In fact, you will find loads of commits with nonsensical messages and no bodies, many of whom get reverted.

Let us take this snippet:

444a200eef33193403ab Upload files to "img"
2a672fac30cd3a7b5780 revert 9e223759e0e804514c318f282038fc0e12c8a180
9e223759e0e804514c31 Add img
b5c02fe14a0739add8e7 Update
36ede797f3dd48c85402 Vulkan validation error fix.
ce8f3e802edc216caebf Use proper SPDX-FileCopyrightText for Sudachi. Corrects db647d915d
040893da00a4d42f9737 formatting
876d7f90b60e3fed88b6 Add option to log synchronously, add tooltip to log filter.
db647d915d575a995f9b Including sudachi Emulator Project as a copyright owner.
b3e989343dd0702e302a Added support for Princess Peach: Showtime!
09578d522b203458d819 revert 925ce2fad34aeb890103d52ea50c151a94fdcd1f
9b77efe2b43ef8d93e89 revert eb306775c616c4ba6db5fadafd19b509ae16f709
224cac988e0bd275265f revert fd1ec51496917ee656e9405826d3d26444b2a04c
43c1a4c64349243e9f06 revert 77e9b7b59bb31bf93e4d2156e614f8944cc542af

Keep the sudachi copyright commits in the back of your head for now.

From such commit history, most professional software developers would stay away from such an open source project that is lacking basic standards. There is clearly a complete misuse of git's capabilities that currently make tarballs and a look more attractive.

In addition, this should not be touted by the suyu team as activity to prove that the project is alive and well. If anything, it proves quite the opposite due to many of these daily commits being nothing-burgers.

I cloned it: I own it

Suyu has seen itself in hot water for violating the GPLv3 license that yuzu has long been using. Initially, the entire project got relicensed under MIT without anybody's permission with commit f1e4595ebfa73e3ffb7f ("Initial commit").

Such important change is buried between others that simply add fuel to the fire:

be9cfdc37de8d48c707a try 2 of fixing centering on readme
c53d25fa86cb01754046 try 1 of fix readme centering issues
2562e0248b71a0858104 fixed issue #1, changed to GPL v3
868116c86ad8445bf866 updated metadata of readme
c24e8c27d26c0c1280d6 added logo
6323c345cd79baaf4efb added linux legacy artifacts download
e4bcdb21fb1bc83a7233    deleted:    LICENSE.txt
154cf56b0dfd0d788ee8  On branch master  Changes to be committed:        modified:
7687666d32dab3cd8249    modified:
f8eb42e2930bc5da9c8a added legacy artifacts download
f1e4595ebfa73e3ffb7f Initial commit

Even after reverting to GPLv3 (2562e0248b71a0858104 ("fixed issue #1, changed to GPL v3")), the SPDX headers were attacked with a slew of blind s/yuzu/suyu/g, removing the necessary copyright for the yuzu devs.

Somebody on the suyu team was at least smart enough to notice the issue in one of these junk pull requests and has, thankfully, avoided catastrophe. This copyright fiasco might have also affected code imported from sudachi if it was not caught in time.

Disco(a)rding you all

One issue that is plaguing many open source projects by the day, including suyu (and to great extent, LineageOS), is Discord as the sole form of communication between the core team, contributors and regular users.

Discord is not only a well-known proprietary, data-abusive company, but it also gatekeeps users that do not have an account from accessing chat logs that could be essential to them.

While many might see requiring a telephone number for new accounts as a spam protection measure, it is mainly used to track your activity online and sold as personal data. Discord is not the only player in the arena utilizing such tactics to gather user intel.

Those who use only open source software will eventually find themselves unable to contribute, gather help or simply interact with people who share the same interests. It can be considered despicable to use such platform considering the many great alternatives out there (e.g. Matrix,, XMPP, IRC, etc.).

Since the suyu project was now forced to have its own git instance, they require everybody to have a Discord account. Through it, one must message the developers to obtain permissions for creating repositories, which also covers forking to contribute.

While not mentioned, if you do intend to contribute code but lack a Discord account, you can write to admin atsymbolhereIguess suyu dot dev. As usual, be civil and keep spam away from them.


I will quote mikael110 from ArsTechnica's forum to sum up the situation:

If you actually do some research into Suyu it will very quickly become apparent that it is run by kids who have literally no clue what they are doing.

Take their title.keys requirement for example, which is one of the only noteworthy changes from the original Yuzu code. It is implemented using a pure name check. In other words the emulator just requires you to have a file called title.keys. Even a 0 byte file is sufficient. It's apparent that nobody currently on the team seems to have any serious programming skills. Certainly nothing that would enable them to develop an emulator.

The entire project is pretty much a joke, which frankly does not deserve the airtime Ars is giving them.

What they are saying on the forum is true. Sadly, suyu is not only lacking real developers, but is also smearing the source code in ways that make it appalling for external contributors.

At this point in time, suyu would require a hard reset: a force-push with all the commit disasters fixed, actual Pull Request reviews by developers (who know what they are doing) and more open communication without proprietary channels.

There is not much one can currently do for suyu. A force push by somebody might lead us to an xz-like incident of backdoors inside past commits. Yet, considering the dire situation, it is more than possible to pull off a Trojan inside their Pull Requests since there is currently no real understanding of the emulator's source code (and likely source code in general) by most of the team.

I hope the suyu project will, eventually, get somebody on board that has enough expertise to steer everything in the right direction. Otherwise, sudachi or yet another fork of yuzu's code will be required to keep the Switch emulator scene diverse and going.

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:

  • boot
  • vendor
  • system
  • userdata

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 
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@

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@ dd of=/dev/block/mmcblk0p33
$ dd if=/dev/mapper/loop0p34 | ssh root@ dd of=/dev/block/mmcblk0p34
$ dd if=/dev/mapper/loop0p35 | ssh root@ 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.

Around 8 years ago I purchased a Moto G 2014 LTE: a mid-ranger phone with great speakers and an acceptable SoC for the time.

The reasoning behind my choice was to experiment with Android's codebase and actually get good at Android ROM development, while at the same time push the device's lifetime to its extremes.

After having put away my daily driver Nexus 5X, this was my only phone left together with a Lumia 635 that I would have never for the life of me used ever again, after being forced to use it an entire summer.

On the Moto, unlike the Lumia, I was able to port LineageOS 17.1 after modifying some existing (but very obscure) device trees, yet the final result ended up being quite disappointing.

The UI was bonkers slow in many places and the phone would only be able to keep two apps at best in the background with its 1G of RAM, even after updating the ROM configuration to Android Go; opening apps like Firefox rebooted the handset, Android's VPN support was (and remained for very long) broken and alarms fired later and later with each passing day.

The fixes were hard to find, deep in the 3.4 ACK but eventually made it into my Motorola msm8226 kernel.

After these issues were fixed, I had an usable phone whose form factor was pretty amusing compared to my classmates' 18:9 Xoxomi sm8250 monsters, yet I still had the high ground speakers-wise.

The what OS

One day, through the Moto G's XDA section, I stumbled upon a post about a SailfishOS port.

I knew about SailfishOS already and that it used the same Hybris (or Halium) magic that UBports uses to get a full GNU/Linux OS running on these Android phones, but I have never experienced that “magic” beforehand (the Nexus 5X with Plasma Mobile should not count, being an awfully incomplete experience).

Out of pure curiosity, I decided to try it out; resulting in reverting my cool and updated vendor, kernel and system to CM12.1 for satisfying the Sailfish port... not cool, even though I just wanted to give it a shot (also I still hope that Michał can get it to hybris-14.1).

I really enjoyed the Sailish UI and its performance, although there were very few apps around that were useful (or usable) day-to-day; the lack of Android app support only exacerbated the situation as I did not buy a SFOS X license from Jolla.

I used SFOS with 2 or 3 apps on it for a month or more until I snapped, leading me to head back to Android 10 until I got my new phone.

Generous destiny

My new phone, the Sony Xperia 5, was a tactical choice: an amazing 21:9 display (which felt awkward initially but got to love in 2 days), awesome speakers, fancy cameras, a beast of a sm8150 and, of course, Sony Open Devices support that was not yet available for the 5 II at the time, making me settle for this one.

Getting a Sony of this caliber meant getting involved with their Open Devices program, as high-end devices do not get the same amount of love as the cheaper (or older) ones. The scope of this all was to bringup SailfishOS, although I was very skeptical of doing so at the time, especially due to the (then extremely old) Ubuntu 14.04 chroot required for building the OS; this lead to the port of the latest LineageOS to escape from the stock ROM's Google hell, making it, indeed, a breath of fresh air.

Build and... Build, until it's done

Time passes by and almost a year later, I actually determine myself to port SFOS, but problems arose almost immediately: while cloning the HADK bits onto my separate NVMe drive (being on a laptop with 500G+500G setup), I realized there was not enough space to compile the OS as a HADK-syspart is required; essentially being another clone of Android's sources for mounting /system and /vendor, which are both packaged on the system. As a bonus, me having /home onto another drive meant that sharing things between /srv/mer and ~/hadk was an absolute kidney stone to deal with.

After several days of recompiling and rebuilding the rootfs over and over because building SFOS is not straightforward nor portable, the different, freshly formatted with Ubuntu AMD Zen machine finally finished baking a flashable image.

Of course, the first boot was an absolute failure and was stuck at the SFOS logo for several minutes, only for the screen to then turn black; it turned out that my /system and /vendor were empty.

After fixing that by modifying hadk-syspart and rebuilding yet again, it finally booted and got to the tutorial: I knew that the port worked now (except a few things like vibration, notifications and the fingerprint sensor that would be fixed later on) and I could finally try the latest and greatest SailfishOS.

Coincidentally, a few days later I heard about Waydroid, seemingly an enhanced Anbox so that OS like UBports and Sailfish could spawn an Android container to use its apps without much of a hassle. Several days of troubleshooting passed by to get it working, leading to this commit.

Precarious... apps and whatnot

One of the first things I usually do when installing a new OS is configuring my Nextcloud account to sync contacts, files, notes, tasks and so on. I knew Sailfish has added Nextcloud support not so recently and was eager to try it out: of course, it lacked support for Nextcloud's 2FA authentication thus requiring a one time access key to login.

I really thought Bob's my uncle then, but no way in hell life was going to be that easy.

There is no built-in way for me to sync my files, tasks, nor notes which I thought would go into the Notes app: people on the forums complained that built-in backups were broken too, but I had yet to see it fail.

This lead me to go on a hunt through Openrepos and Chum for Nextcloud-syncing apps: the Nextcloud Notes app wanted me to register with my Nextcloud account (again) and then stored the credentials unencrypted on the device, but it was my only option to sync the notes.

For my tasks, there is an app just called “Tasks” that, yet another time, asked me to register, but at least this one stored credentials with sailfish-secrets; the app was abandoned by the author due to certainly understandable “uncertainty about SailfishOS's future”.

The obvious question to put out there: why was the Sailfish Accounts API blocked from Harbour (and left without documentation) until 4.5.0, leaving developers with practically no goddamn API for accessing the accounts registered within the system settings? It is patently stupid to login a thousand times on your account, across different apps with different mechanisms to store credentials, merely to keep things in sync.

As for the files, only two choices were left on my table: ghostcloud, which seemed like an abandoned app and was forked only to be rebuilt for aarch64, and sailsync owncloud, which also seemed pretty dead but at least it offered an official aarch64 package; that alone made it my choice in this case. I had to manually add a folder from my Nextcloud to the corresponding path locally, something I found to be perplexing for people like me who store hundreds of folders. I went ahead and continued using this, but it remains a certainly clunky way to sync (and find) files.

For my IMs of choice, I had to (obviously) download a Matrix and XMPP client to use: Sailtrix started to become acceptable only after many months of usage (was borderline unusable until 1.3.5), but I still need to manually accept giving it access to my “secrets” every time it is opened. After setting it up, though, I had a really bad time figuring out why my messages were not appearing in order by date, with “unverified MegOLM session” and “possible replay attack” replacing every single message in my encrypted conversations. All my glaring issues with encryption would magically disappear with the “Clear cache” button. Like a cherry on top, chat history did not function and downloaded files did not even appear on the system at all.

As for XMPP, Schmoose had no OMEMO support in its OpenRepos version (yet) so in order to get it, I had to go to their GitHub release page and install some early beta which lacked a variety of essential features, for example a menu to view, accept or discard OMEMO fingerprints; this app also stores credentials unencrypted and mandates the press of the login button every time you open the app.

“Privacy” and (in)security

Do you (or would like to) use a custom DoH or DoT DNS? Luckily for you (/s), this is not supported in SFOS and the OpenRepos-packaged dnscrypt-proxy is broken and does not ship any systemd service; you can work around this by installing the program manually from GitHub.

Tor browser user? You might be satisfied by Waydroid, otherwise enjoy an extremely outdated, Gecko-based browser pre-installed by default. In case you have wondered: no, you cannot run the Linux version of Tor Browser because QtWayland is so old (Qt 5.6) that it is unable to load 98% of the Linux programs out there. XMPP or Matrix calls? Again, Waydroid.

I was beginning to get fed up with the general sentiment about SFOS: why is it getting all this praise after all? Why is everybody seemingly ignoring the precarious apps situation and just buying Jolla licenses for Android App Support, to use this OS as (like someone on the forums put it) “a fancy GUI launcher”? Why is there almost zero mention of its outdated components and proprietary parts?

Yes, SailfishOS packs proprietary apps and components that were promised to be FOSS, but even after a decade, very little was delivered and less is to come. The outdated components situation, like them shipping an incomplete hybrid between Qt 5.4 and 5.6 because “they are scared of licensing issues” is a problem Jolla dug itself into; a problem they could have avoided completely if everything was open source from the start.

One particular example that struck me, demonstrating the consequences of SFOS' licensing strategy, can be found on the forums, where a developer is hitting a serious Qt bug that breaks their app, so they have to pretty much rewrite the patches from upstream Qt for the bugfixes to be backported on Jolla's Qt fork; the bug was small, but way bigger and more numerous ones would make this dangerously unsustainable.

I do not think that app developers should find adjusting their workflow hard between an updated Qt6 toolkit and Qt 5.6 plus Silica (which is fairly documented), but on top of this outdated, relatively feature lacking Qt base, Harbour is also very strict in the set of libraries that can be used, effectively knee-capping app developers. Many left the platform exactly because of this: unmaintained promises and increasingly outdated software on the platform.

On a more technical side, SFOS also suffers of these issues:

  • LUKS1 home encryption, easily bruteforce-able
  • LUKS1 password is not separate from the lockscreen password (break once, broken twice)
  • Woefully outdated components
  • Inability to run standard GUI Linux programs due to the above bullet point
  • Proprietary info-sensitive apps like Dialer and Email (unable to be replaced)
  • No OMEMO in built-in XMPP support (some report that XMPP stopped working altogether...)
  • Outdated Android base and kernels for its devices due to fear of breakage
  • Proprietary essential but banal features
  • Lack of WLAN MAC address randomization (talk about not being tracked huh...)
  • Very limited choice of libraries for Harbour
  • Lack of ability to lock to certain cellular technologies (e.g. LTE), allowing for downgrade attacks

For the Android base and kernel side, let me explain how it works: Sailfish X (product of Jolla) bases itself on the Sony Open Devices Project, which every now and then updates its kernels (e.g. from 4.14 to 4.19, thanks to its contributors) and keeps the device's proprietary blobs updated, so that you are able to build and install newer Android versions than the stock, provided by Sony one.

The 2016 Xperia X, released with Android 6 can be updated up to Android 9, which brings a kernel bump from 3.10 to 4.4 (which went EOL in early 2022).

Jolla is unwilling to get off the SODP Android 6.0 base with the old 3.10 kernel, meaning that people are left vulnerable to some Android 6.0 vulnerabilities which can be found in HALs, some components that are consumed by hybris, device blobs and especially the kernel, which has accumulated a nice amount of CVEs ever since the 3.10 branch of Sony's kernel was last updated.

As for the essential but proprietary features, as an example, the fingerprint scanner functionality is a very simplistic “bridge” between Sony's FOSS fingerprint HAL running in the Android space and the SFOS userland, yet the package to enable such feature is kept proprietary and is distributed through a repository named hw-common; a replacement FOSS package exists but is Android-base specific, meaning it has to be built locally.

While fingerprints are a convenient but unessential feature, VoLTE support is nowadays becoming indispensable and must also suffer such faith: not only is most of the lifting happening in the Android space through blobs, but a required package has not been made available on hw-common for porters and is thus heading the OS backwards overall with regards to open sourcing.

While I do compile my own LOS with microG and patch the OS to increase its privacy, putting it light years ahead of SFOS, I am aware that not everybody has access to the 7 yottabytes of disk space and RAM required to compile Android. In that case, you could just install CalyxOS (or even GrapheneOS) and enjoy a lean, fast and quite worry-free Android experience. If you do not own a supported device for either OS, you could try porting either yourself to other devices as it is AOSP after all, but that would require you to compile it. GSIs also exist for the LOS route.

“Community” project

Like with every Linux project out there, users of such products will continuously attempt to push a “community-centered” ideology, even when potential contributors are barred from accessing certain source code and users might not have many choices or even be heard. A good, healthy community obviously helps immensely with the spread and usage of an OS (or distro), but that does not always apply.

To start, the SailfishOS forum users seems to suffer from the Moronix intellection (article coming someday), already setting the bar very low for any community. Such environments are usually toxic, filled with ignorance and get political exponentially fast, losing technical substance in the way and gatekeeping potential contributors.

As for the developers centered around SFOS, I can say that they have been so far respectful, educated, helpful and enjoyable people to chat with on IRC channels or whatnot. Some might not look like this and be exaggeratedly vocal (ahem, in the “other” fanclub), but they are the adjectives mentioned above.

Le conclusion

Before you draw any negative conclusions, keep this quote in mind:

You always hurt the one you love

I would absolutely love to carry a GNU/Linux machine in my pocket and just use it as a regular, actually FOSS, phone.

But this? This is just deceptive marketing and I have no idea how the SFOS userbase is seemingly enjoying being stepped on by Jolla.

Most people on the forum seem to forget that Jolla is a company (even Sami in their 2021 “Sailing for 10 years” talk refers to the OS as “the asset”): them selling their OS to authoritarian governments, like the Russian one, is simple business, until the EU sanctions backfired in early 2022.

As much as SFOS users scornfully vilify Android and iOS, you cannot deny that they are light years ahead in terms of performance, privacy and even security: Jolla being a “small company” has very little to do with the artificial roadblocks they have put in place.

As for me, at the time you are reading this post, I have either decided to keep SailfishOS and kind of explore the (accessible) codebase and maybe did a few contributions to both apps and system or just lost my patience and got back to Android.

Thanks for sticking along.

P.S. Thaodan made the initial port for the Sony kumano platform, I just added the support for the Xperia 5 based off of the existing configuration and upgraded the Android base.

If you do a lot of heavy lifting in git, sometimes when doing interactive rebases you might find yourself something like this:

pick 9cc0da4925b0 (fixup): Some fix for 272
pick fe9f818cd53e (fixup): Another fix for 272
pick 23da7ec20187 treewide: Linux 4.14.272
pick d751d28e01a2 (fixup): Fix yet again for 272
pick be4acf7ee8dd treewide: Linux 4.14.273

Moving around downstream

The above recently happened to me when trying to upgrade my phone's 4.14 kernel to its latest kernel SUBLEVEL. As Qualcomm uses its own fork of the kernel (usually called CAF or vendor kernels), merging mainline linux-stable branches becomes fairly painful due to downstream commits, which are either from CAF itself or from the ACK (Android's Common Kernel) that end up generating merge conflicts.

Once you fix a merge conflict, you commit the fix locally and continue with your merging until you reach the end of it; you then combine the fixed commits into the commits they are supposed to be in. This could lead to you slipping into the situation here described, where you have a commit or more that must be merged into a specific commit.

Turning against the flow

As simple as a fixup sounds, if you read the comments towards the end of your editor, you will find:

These lines can be re-ordered; they are executed from top to bottom.

Which means we are trying to do it the opposite way here: we want our commands to be executed from bottom to top, so fixups can be issued towards the next commit, not the previous one.

In the best case scenario, we can move the two commits we want to be merged (9cc0da4925b0 and fe9f818cd53e) below the commit you want them merged into (23da7ec20187), but because we are dealing with a massive codebase here, this very likely will result into more painful merge conflicts because some code might either be missing or be already present. At worst, you can fixup everything above d751d28e01a2 and pick 9cc0da4925b0, manually copy the commit message from 23da7ec20187 and paste it at the end of the magic mixing.

There is this other command that you might have (like me) ignored until now: squash, which unlike fixup, it merges the specified commits into one but keeps the commit messages of them all. This seems to be especially useful in case you want to reword your final commit on the fly. In our case, instead of following the worst case scenario above, we can just delete the irrelevant commit messages and keep the one we want.

f is for Fixdown

There is, though, a trick to do the above more easily: fixup, yet again, but with a catch.

If you carefully read through git's rebase-todo file in the editor, in the commands section you should see:

f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor

That flag is just what we needed! Instead of throwing away the fixed-up commit's message, by using -C, we can use that specific message when the melding is complete. To solve our commits issue at the beginning, simulating a bottom to top execution order, we have now obtained this:

pick 9cc0da4925b0 (fixup): Some fix for 272
f fe9f818cd53e (fixup): Another fix for 272
f -C 23da7ec20187 treewide: Linux 4.14.272
f d751d28e01a2 (fixup): Fix yet again for 272
pick be4acf7ee8dd treewide: Linux 4.14.273

What happens here is interesting, but also simple to understand:

  1. Merge fe9f818cd53e into 9cc0da4925b0 and throw away the commit message of the first.
  2. Merge 23da7ec20187 into 9cc0da4925b0 but keep the commit message of the first, overriding 9cc0da4925b0's message.
  3. Finally, merge d751d28e01a2 into 9cc0da4925b0 and discard its commit message.

Ahead of the curve

The final result is a clean commit with the message treewide: Linux 4.14.272 that contains the 3 commits melded into one, with very few to no merge conflicts happening during the operation. This is especially relevant as the possibility of merge conflicts increases dramatically with larger codebases, due to the number of small commits creeping up to ease code reviews.

If you want to be extra sure of what you are doing during the fixups, you can also use -c to open up the text editor to view what is going to happen with the commits.

Some time ago I have been going full-in into my Prosody server and have finally reached a point where the server passes almost all of the tests on Conversation's XMPP compliance website (you can run the caas program locally).

All it is missing are the following tests:

XEP-0368: SRV records for XMPP over TLS                          FAILED
XEP-0411: Bookmarks Conversion                                   FAILED

Informational tests:
XEP-0077: In-Band Registration                                   FAILED
XEP-0157: Contact Addresses for XMPP Services (Abuse)            FAILED

As for the Informational tests, I know they will not pass because this is not a public server.

But why did the two above fail then?

The decentralization dilemma

I have been an XMPP user since I dipped my feet into the world of federated/decentralized messengers and so far, it has been great.

I started out with Matrix though and eventually moved to XMPP, then went back to Matrix for various reasons (mostly because I deployed then totally alpha-quality Dendrite instead of Synapse for quite a while).

Most importantly, the reason I switched to XMPP was because of its totally decentralized nature: XMPP is somewhat like e-mail, where everyone hosts their own server “independently” from other servers and carry their own share of issues or fixes.

We all know that is the biggest Matrix homeserver, with the most amount of users and rooms, disproportionately bigger than any other homeserver (because it is run directly by the peeps over at the Matrix Project).

Believe it or not, this is sort of a good thing: what this allows the Matrix people to do is to update frequently and “force” other homeservers to update relatively quickly as to not break federation or features with

This is actually great for an ecosystem like the open source one, where things move fast and allows everyone to be on their same level, maintaining a high standard of quality.

You probably see where this is going...

“The ecosystem is moving”

You probably have already read Moxie's opinion on why they chose a non-federated service for Signal; here are some snippets from the article that explains my frustrations with XMPP:

We got to HTTP version 1.1 in 1997, and have been stuck there until now. Likewise, SMTP, IRC, DNS, XMPP, are all similarly frozen in time circa the late 1990s.

What we have instead is a complicated morass of XEPs that aren’t consistently applied anywhere. The implications of that are severe, because someone’s choice to use an XMPP client or server that doesn’t support video or some other arbitrary feature doesn’t only affect them, it affects everyone who tries to communicate with them. It creates a climate of uncertainty, never knowing whether things will work or not. In the consumer space, fractured client support is often worse than no client support at all, because consistency is incredibly important for creating a compelling user experience.

The above could not be more true.

XMPP is fairly stagnant and my favorite example for this is OMEMO, the E2E encryption that is commonly used for chats: the XEP is at version 0.8.3 as of mid-2022 but none of the clients implement that version because “it breaks compatibility with the older OMEMO 0.3.0” (used by every XMPP client except UWPX that today sports 0.8.1 and is a Windows-only client).

The mentality behind this is essentially “if nobody uses it, don't implement it”, dragging the whole ecosystem into stagnation even though both 0.3.0 and today's 0.8.3 could co-operate on the same client at the cost of added client complexity.

This gives the impression that OMEMO is not actually complete as 0.3.0 does not contain any mention of the double-ratchet algorithm, threat models or usage in MUC rooms.

As an added bonus, the C Signal library implementing OMEMO is being forked over and over as upstream is dead in favor of a new Rust library, leading to projects reinventing the wheel and having different featuresets for OMEMO.

Other honorable mentions for the failing tests at the beginning are XEP-0368 and XEP-0411: passing the test for the first XEP requires you to specify in Prosody legacy_ssl_ports = { 5223 } so that clients could connect to that port via direct TLS, but the only mention of 5223 that I was able to find is in XEP-0035, which was last updated in 2003 (19 years ago...) and explicitly mentions:

Accordingly, the use of port 5223 and port 5270 for secure sessions is deprecated.

Direct TLS was also not implemented at all in Prosody 0.11 and earlier, so when version 0.12 started hitting other servers it left me unable to communicate with them with a generic “Delivery failed”, and clients like Gajim would not let you log in, marking XML streams as ill-formed.

Meanwhile, the latter test requires you to enable the mod_bookmarks module, which was deprecated years ago and was for clients that utilized XEP-0048 when nowadays we have XEP-0402.

Thankfully, XEP-0411 was removed from the caas test suite earlier this year as it was deprecated in 2021, but was kept around for a painfully long while.

The initial bringup

Setting up an XMPP is (IMHO) an absolute nightmare between configuring DNS records correctly, reading whole XEPs as other online documentations are outdated, choosing the correct modules (at least in Prosody's case) and so forth.

Then come the tests which verify outdated settings and make you look and feel bad in case you do not pass them, which is especially true for public servers.

Matrix, meanwhile, has the advantage of being very simple to deploy, almost all of the clients support the necessary MSCs and the documentation for server admins is “OK”.

While there are rumors stating that the actual Matrix spec is painful to read, being able to deploy a homeserver without reading it is a very welcome addition.


So far, Matrix is a good “replacement” for XMPP, even though I would love for both of them to co-exist, although XMPP in its current state and current (now becoming niche) community does not give any high hopes for a bright future.

My personal worry with Matrix is that the spec might become excessively big and writing clients will become a very hard task to include all its features.

So far, writing new Matrix servers is already a humongous task and demonstrates why everyone is leeching onto Synapse and not testing/contributing to alternative ones like Dendrite, Conduit or Construct.

On the metadata side of things, it seems that P2P Matrix will eventually become the “standard” version of Matrix and that will greatly reduce the amount of metadata that is shared over homeservers unencrypted.

I hope for the best for both projects and thanks for reading!

As someone who runs CentOS Stream on his production server and Fedora on his desktop, I recently had the need for a CentOS container where I could just “try stuff out”.

Because I was used to LXD's simplicity and practicality, where I could just lxc launch images:centos/8-Stream, having to bring myself to use only things based around (and made for) the RHEL/Fedora ecosystem meant switching from LXD to systemd-nspawn: a complete pain in the ass, especially because of one missing feature: downloadable images.

If you need to setup a quick container with distro X, the command above will do the job on LXD, but with nspawn the only users/guides I saw using it just debootstrapped a Debian/Ubuntu chroot and coped with that; users/guides on the Internet that show how to set up Arch (or even things like OpenSUSE) are incredibly rare, although you can find the how-to inside the systemd-nspawn(1) manual.

Having just come across the mkosi tool by the Lennart himself, I figured that I could kinda automate setting up nspawn containers by using this tool. According to the man page, the following distros are supported as of 2/1/2021:

fedora, debian, ubuntu, arch, opensuse, mageia, centos, centos_epel, clear, photon, openmandriva, rocky, rocky_epel, alma, alma_epel

I perfectly know that the tool just uses debootstrap, zypper, dnf and so on to basically create a chroot, but this is pretty much what I needed: an lxc launch images:X kind of command where the container is ready to use (or really close to that).

Show me how!

As we want to set up a CentOS Stream 8 container here, because there is (currenly) no way to immediately to do that, we will install the regular CentOS Linux 8 distro and upgrade it to Stream 8.

The obvious first step to do so is installing the actual mkosi tool, which would pull other package managers like pacman and zypper with it.

Now, I want the rootfs to sit inside a folder named c8s and to do that, we need to do the following:

sudo mkosi -d centos -r 8 -t directory -o c8s --package=@minimal-environment --password=weed

The command above does the following things:

  • Use the CentOS distro
  • Release version “8”
  • “directory” type aka create a rootfs as a plain directory
  • Make “c8s” the output directory name of the generated rootfs
  • Install the @minimal-environment group to pull dnf along with many other “regular CentOS install” tools
  • Set the root password to weed

As Fedora is using enforcing SELinux (I HOPE), we need to relabel things inside the rootfs to not be denied some actions by the host when inside it:

restorecon -Rv c8s

Now, boot the container with the usual:

sudo systemd-nspawn -bD c8s

And login as root with password weed. Before touching dnf, we need to fix the rpm database as it results broken after the install (maybe because mkosi installs things like it was a mock container), so we need to:

rpm --rebuilddb -vv

After that, add a DNS to resolv.conf e.g. with echo nameserver > /etc/resolv.conf and do a dnf upgrade to check for any weird errors/warnings coming from the package manager: it should not give anything.

Now we just need to follow the regular c8 –> c8s upgrade procedure:

dnf swap centos-linux-repos centos-stream-repos
dnf distro-sync

Exit out of the nspawn container and you should be all set! Have fun with your fancy CentOS Stream 8 container!

Hello, first time writing a blog :)

Sony Open Devices offers you the chance to port new operating systems onto Sony devices by trying to keep the system blob-free and make the Android ecosystem somewhat sustainable.

They also provide instructions on how to build AOSP and downloadable binaries which is pretty neat.

Because I hate XDA, I built LOS from SODP sources and published the builds here.

I would like to thank @MartinX3 (for the initial 17.1 port) and the SODP team for making all of this possible :)

Pre flashing

As usual when installing a ROM, be sure your bootloader is unlocked and you have a backup of your data if you want to restore it or in case something goes wrong.

I cannot guarantee that this ROM will work for you, you have been warned

Before using this ROM you need to do the following because of this bug:

  • Download your phone's carrier specific latest firmware for your model through XperiFirm, the firmware version should start with (Android 11)

  • Flash the firmware through newflasher

  • Download an older firmware (Android 10) through XperiFirm and save into another folder

  • Remove all the files inside the firmware folder except the modem one (it will probably be named modem_X-FLASH-ALL-A2CD.sin)

  • Flash that single file through newflasher

If you think you are safe by having all of the firmware bits installed you are wrong, because various things like bluetooth will be broken.


You can download the build for your phone here (there are both microG and vanilla LineageOS builds) and flash it with the following instructions.


You need to be familiar with building LineageOS in the first place; there are some quirks in this port that need to be resolved before starting to compile.

The manifests you need are located here.

The build you will obtain will not include features like LiveDisplay, Night Light or microG: you will have to add these features yourself.

Now you will need to:

  • rm -rf hardware/lineage/interfaces/cryptfshw

  • Apply the following patch on

project vendor/oss/repo_update/
diff --git a/ b/
index 24769b1..dd45d1d 100755
--- a/
+++ b/
@@ -59,12 +59,6 @@ if [ "${SKIP_SYNC:-}" != "TRUE" ]; then
     repo sync -j8 --current-branch --no-tags
-enter_aosp_dir bionic
-# Add inaddr.h header file
-# Change-Id: Iad92c39fb729538cf51bf9d9037b15515104b453
-apply_gerrit_cl_commit refs/changes/84/1582884/1 5efdad358c77795bef6c011d87625b0a46b0bd0d
 enter_aosp_dir vendor/qcom/opensource/data/ipacfg-mgr/sdm845 hardware/qcom/sdm845/data/ipacfg-mgr
 # guard use of kernel sources
 # Change-Id: Ie8e892c5a7cca28cc58cbead88a9796ebc80a9f8
@@ -83,27 +77,6 @@ enter_aosp_dir hardware/qcom/wlan
 apply_gerrit_cl_commit refs/changes/49/1532349/1 c889ca2c5503fb955f276c4899924c324effd8cc
-enter_aosp_dir frameworks/base
-# Fix bug Device that can't support adoptable storage cannot read the sdcard.
-# Change-Id: I7afe5078650fe646e79fced7456f90d4af8a449a
-apply_gerrit_cl_commit refs/changes/48/1295748/1 6ec651f12a9b67a9d2e41c2fe4d9a71c29d1cf34
-# SystemUI: Implement burn-in protection for status-bar/nav-bar items
-# Change-Id: I828dbd4029b4d3b1f2c86b682a03642e3f9aeeb9
-apply_gerrit_cl_commit refs/changes/40/824340/3 fcc013282943c935af8225a914a525e996d42866
-enter_aosp_dir build/make build
-# releasetools: Use du -b
-# Change-Id: I1955261de0f6323518b214e2731ef4879c3304e0
-apply_gerrit_cl_commit refs/changes/03/1269603/1 96a913e7f4eceb705b4e6862068117670ce31b79
-enter_aosp_dir system/vold
-# Switch to exfatprogs compatible fsck parameter
-# Change-Id: I2c436816a293a36fc9f0cd635cdb9ca3b5f88bfc
-apply_gerrit_cl_commit refs/changes/37/1441937/1 2035a83916914ec8c6ecaacb6f23ea5256be2edd
 # because "set -e" is used above, when we get to this point, we know
 # all patches were applied successfully.
 echo "+++ all patches applied successfully! +++"

  • Apply the following patch on snippets/lineage.xml:

diff --git a/snippets/lineage.xml b/snippets/lineage.xml
index 673d28a..f623e97 100644
--- a/snippets/lineage.xml
+++ b/snippets/lineage.xml
@@ -70,19 +70,19 @@
     <!-- add guard for AOSP hardware/qcom dir -->
     <linkfile src="" dest="hardware/qcom/" />
     <!-- for AOSP sdm845 and sm8150, we override -->
-    <!--<linkfile src="" dest="hardware/qcom/sdm845/" />-->
-    <!--<linkfile src="" dest="hardware/qcom/sm8150/" />-->
+    <linkfile src="" dest="hardware/qcom/sdm845/" />
+    <linkfile src="" dest="hardware/qcom/sm8150/" />
     <!-- add guards for CAF repositories -->
-    <linkfile src="" dest="hardware/qcom-caf/apq8084/" />
-    <linkfile src="" dest="hardware/qcom-caf/msm8916/" />
-    <linkfile src="" dest="hardware/qcom-caf/msm8952/" />
-    <linkfile src="" dest="hardware/qcom-caf/msm8960/" />
-    <linkfile src="" dest="hardware/qcom-caf/msm8974/" />
-    <linkfile src="" dest="hardware/qcom-caf/msm8994/" />
-    <linkfile src="os_pickup.bp" dest="hardware/qcom-caf/msm8996/Android.bp" />
-    <linkfile src="" dest="hardware/qcom-caf/msm8996/" />
-    <linkfile src="os_pickup.bp" dest="hardware/qcom-caf/msm8998/Android.bp" />
-    <linkfile src="" dest="hardware/qcom-caf/msm8998/" />
+    <!--<linkfile src="" dest="hardware/qcom-caf/apq8084/" />-->
+    <!--<linkfile src="" dest="hardware/qcom-caf/msm8916/" />-->
+    <!--<linkfile src="" dest="hardware/qcom-caf/msm8952/" />-->
+    <!--<linkfile src="" dest="hardware/qcom-caf/msm8960/" />-->
+    <!--<linkfile src="" dest="hardware/qcom-caf/msm8974/" />-->
+    <!--<linkfile src="" dest="hardware/qcom-caf/msm8994/" />-->
+    <!--<linkfile src="os_pickup.bp" dest="hardware/qcom-caf/msm8996/Android.bp" />-->
+    <!--<linkfile src="" dest="hardware/qcom-caf/msm8996/" />-->
+    <!--<linkfile src="os_pickup.bp" dest="hardware/qcom-caf/msm8998/Android.bp" />-->
+    <!--<linkfile src="" dest="hardware/qcom-caf/msm8998/" />-->
     <linkfile src="os_pickup_qssi.bp" dest="hardware/qcom-caf/sdm845/Android.bp" />
     <linkfile src="" dest="hardware/qcom-caf/sdm845/" />
     <linkfile src="os_pickup_qssi.bp" dest="hardware/qcom-caf/sm8150/Android.bp" />

You should now be ready to build.

As an example, here's how to build for the J8210 (Xperia 5 single SIM):

source build/
breakfast lineage_j8210-userdebug
make -j4 dist # or whatever is your number of threads


  • No LiveDisplay