Christians Tagebuch

The latest posts in full-text for feed readers.


Android 14: Modify system partitions

I own a Fairphone 4 with the stock camera apk that I extracted from official firmware in 2023-11. In 2024-04, Fairphone released a major camera update and I wanted to get it on my LineageOS-based system.

Problem

A forum post describes that the .apk extracted from the firmware fails to install:

adb: failed to install FPCamera.apk:
 Failure [
  INSTALL_FAILED_MISSING_SHARED_LIBRARY: Reconciliation failed...:
  Reconcile failed:
  Package com.fp.camera requires unavailable native shared library libtctcameraalgo_jni.tct.so;
  failing!]

The new camera app (v7.00.04.0007.7.0)requires a shared library that is not available on my LineageOS firmware.

Extracting libtctcameraalgo_jni.tct.so

I extracted the current FP4-TP2L-factory.zip firmware image just as described in my previous blog post with unzip, simg2img and lpunpack. Then I mounted all partitions and looked into their file lists; it was system_ext that contained the library at

system_ext_a/lib64/libtctcameraalgo_jni.tct.so

(40kiB, md5 hash 3daaf896f43a33c7a883f57c7c4be3d5).

How not to modify the system partition

After running adb root I remounted the system_ext partition to be writable:

$ adb shell mount -o rw,remount /system_ext

This was how you did it in the pre-Android 10 days, and it does not work anymore because of dynamic Android partitions which are just as large as they need to be to contain the data:

$ adb push libtctcameraalgo_jni.tct.so /system_ext/lib64/
libtctcameraalgo_jni.tct.so: 1 file pushed, 0 skipped. 53.5 MB/s (40792 bytes in 0.001s)
adb: error: failed to copy 'libtctcameraalgo_jni.tct.so' to '/product/lib64/libtctcameraalgo_jni.tct.so': remote write failed: No space left on device

$ adb shell
FP4:/ # df -h /system_ext/
Filesystem      Size Used Avail Use% Mounted on
/dev/block/dm-3 392M 390M  1.2M 100% /system_ext

I opened a thread on reddit and was told by TimSchumi that I should use adb remount. That command creates overlayfs mounts that lets you modify the system partitions by modifying an "overlay" directory, which is then loaded onto the actual partition.

I also found the magic_overlayfs Magisk module, but deemed that using native tools would be easier. It had a nice short explanation for the problem, though:

On Android 10+, system partitions might no longer be able to remount as read-write. For devices using dynamic partitions, it is nearly impossible to modify the system partiton as there is no space left.

Remounting

$ adb remount
AVB verification is disabled, disabling verity state may have no effect
Using overlayfs for /system
Using overlayfs for /system_ext
Using overlayfs for /product
Using overlayfs for /vendor
Using overlayfs for /odm
[libfs_mgr] __mount(target=/system,flag=MS_PRIVATE)=-1: Invalid argument
Remounted /system as RW
Remounted /system_ext as RW
Remounted /product as RW
Remounted /vendor as RW
Remounted /odm as RW
Remounted /vendor/dsp as RW
Overlayfs enabled.
Remount succeeded
Now reboot your device for settings to take effect

Simply rebooting, creating the lib64 directory in the upper layer of the overlay and pushing the .so file into it did not work:

$ adb reboot
$ adb shell mkdir /mnt/scratch/overlay/system_ext/upper/lib64/
$ adb push libtctcameraalgo_jni.tct.so /mnt/scratch/overlay/system_ext/upper/lib64/
$ adb shell
FP4:/ $ ls /system_ext/lib64
ls: /system_ext/lib64: Operation not permitted
1|FP4:/ $ ls -lah /system_ext/
total 63
drwxr-xr-x  2 root root  4.0K 2009-01-01 01:00 lib
d?????????  ? ?    ?        ?                ? lib64

FP4:/ $ ls -lah /system_ext/lib64/
total 5.0K
drwxrwxrwx 1 root root 3.4K 2024-11-13 21:49 .
drwxr-xr-x 1 root root 3.4K 2024-11-13 21:46 ..
-rw-r--r--  1 root root 256K 2009-01-01 01:00 com.qualcomm.qti.dpm.api@1.0.so
-rw-r--r--  1 root root 141K 2009-01-01 01:00 lib-imsvideocodec.so
-rw-r--r--  1 root root 259K 2009-01-01 01:00 lib-imsvt.so
-rw-r--r--  1 root root  15K 2009-01-01 01:00 lib-imsvtextutils.so
-rw-r--r--  1 root root  32K 2009-01-01 01:00 lib-imsvtutils.so
[...]
-????????? ? ?    ?       ?                ? libtctcameraalgo_jni.tct.so

After modifying the upper layer directory, I had to reboot the phone every time to make Android see the changes properly. The reboots fixed the permission problems.

Library registration

When libtctcameraalgo_jni.tct.so was at the right place, installation still failed with

Package com.fp.camera requires unavailable native shared library libtctcameraalgo_jni.tct.so

I then read that libraries need to be registered - pm list libraries did not list it.

In order to add library to vendor partition, you need to create text file which lists all added libraries. Since Android N (7.0) or later, apps are only allowed to access libraries on a specific whitelist of NDK libraries.

We can create counterpart file for vendor and create it in following path: /vendor/etc/public.libraries.txt.

I moved the library from system_ext/lib64 to vendor/lib64/ because that one already had a etc/public.libraries.txt that I could modify:

$ adb shell
1|FP4:/mnt/scratch/overlay/vendor/upper/etc # echo libtctcameraalgo_jni.tct.so >> public.libraries.txt

Then I rebooted, and all broke down. The phone was stuck at the "Fairphone" boot logo and would not advance any further.

Things I tried

Fastboot and recovery were available, but I did not find a way to disable the remount overlays from there.

The previously linked Implement dynamic partitions writes

For developers using eng or userdebug builds, adb remount is extremely useful for fast iteration. Dynamic partitions pose a problem for adb remount because there is no longer free space within each file system. To address this, devices can enable overlayfs. As long as there is free space within the super partition, adb remount automatically creates a temporary dynamic partition and uses overlayfs for writes. The temporary partition is named scratch, so don't use this name for other partitions.

I tried to delete the partition with fastboot, but that did not help:

$ fastboot delete-logical-partition scratch
Deleting 'scratch'                                 OKAY [  0.009s]
Finished. Total time: 0.009s

In recovery adb, I also tried to delete the scratch partition but did not even find it. lptools_new allows to list dynamic partitions:

FP4:/ # lptools_new_arm64  --get-info
Slot: 0
Suffix: _b
Path to super: /dev/block/by-name/super
Group: qti_dynamic_partitions_b
Arguments: --get-info

GroupInSuper->qti_dynamic_partitions_b Usage->5422981120 TotalSpace->6441402368
NamePartInGroup->odm_b Size->1589248
NamePartInGroup->product_b Size->617897984
NamePartInGroup->system_b Size->971485184
NamePartInGroup->system_ext_b Size->416059392
NamePartInGroup->vendor_b Size->699158528

FP4:/ # lptools_new_arm64  --get-info --group cow
Slot: 0
Suffix: _b
Path to super: /dev/block/by-name/super
Group: cow
Arguments: --get-info

GroupInSuper->cow Usage->5422981120 TotalSpace->6441402368
NamePartInGroup->odm_b-cow Size->1601536
NamePartInGroup->product_b-cow Size->620318720
NamePartInGroup->system_b-cow Size->975286272
NamePartInGroup->system_ext_b-cow Size->417689600
NamePartInGroup->vendor_b-cow Size->701894656

Listing the partitions with fastboot is not directly possible, but partitions can be inferred from the variables:

$ fastboot getvar all
(bootloader) partition-size:super:0x180000000
[...]
(bootloader) partition-type:product_b:raw
[...]

No scratch there, though.

Bitter end

After 3 days I decided to give up and reinstalled the whole operating system. The SeedVault backup automatically reinstalled most applications, but unfortunately some of the applications I use do not support backing up their data:

Conversations did support backups, though.

Published on 2024-11-17 in


Restoring OUYA game purchases

With a bit of setup work, you can again play all the games you bought on your OUYA gaming console.

Back to trial

When the OUYA server shut down in July 2019, many users noticed that the games they paid for now went back to demo/trial mode:

So I just hooked up my ol' Mad Catz M.O.J.O., and was pretty surprised to find that a vast majority of my games that I purchased through the Ouya store no longer work, and act like I've never purchased them

(Sonic 4 episode 1, for example, just launches the "Trial" version now).

glow162, 2019-11-16 on reddit

Soul Power is one of my paid Ouya games that reverted to demo mode since the servers went down.

Would it be possible to recieve a different, working, version of the game if I show you my Ouya receipt?

Starfig the boyo, 2019-08-22 on Twitter

@x10interactive Hi! The Ouya version of So Many Me isn't working anymore, it's reverted to demo mode!

Starfig the boyo, 2019-08-21 on Twitter

Would it be possible to release some kind of patch to play Evocreo on the Ouya?

I bought a copy and no longer have access to it after the server shutdown

Chance Crane, 2019-08-22 on Twitter

@SquareEnixFR @SquareEnixMtl @SquareEnix @SquareEnixUSA

After #ouya shutdown, How can I keep playing Final Fantasy 3 as I bought it and the DRM now keeps me off ? Thanks.

LC, 2019-07-27 on Twitter

A long while back, I bought the Final Fantasy 3 remake on the Ouya. I really enjoyed it and got all the way up to the final dungeon. Things happened and I wasn't able to play it for a long time.

Then Razer shut down the Ouya servers and the game stopped working.

E. Zachary Knight 2019-12-02 on Twitter

I did buy Babylonian Twins and that one also went back to demo mode.

The shutdown casualty list thread on the OUYA Forum lists many games that reverted back to demo mode.

Nice developers

Some developers were very gracious and published a final game update before the store shut down that removed the DRM code and made the game fully free to play.

List of games I know got a final unlock release:

I'm very thankful for them doing that.

Other developers could not do that for various reasons. I contacted the developers of Bloo Kid 2. They would like to have helped me, but the machine that was used to develop and compile Bloo Kid 2 for Android/OUYA crashed and could not be restored :/

Purchases

OUYA's own Purchasing documentation describes how purchases happen on the OUYA:

  1. Game starts up and fetches previous purchases (receipts) from the store
  2. If the game has not yet been bought and the user clicks "buy", a purchase request is sent to the server. The server responds with yes/no and the game unlocks from demo to full version. The purchase is stored on the server and added to the list of receipts.

Encryption

OUYA's creators wanted to make it very hard to tamper with the purchase process. Purchase requests are sent encrypted to the server, and receipt responses are encrypted twice.

For that they needed two kind of key pairs:

Gamer key
Upon registration, the user's OUYA generates a private and a public RSA key. It sends the public key to the server that stores it.
Developer key (game key)
When a game is registered in the developer portal, a private key is generated for this game, and the developer gets the public key. It will be compiled into the game.

When buying a game, the purchase request is AES-encrypted. The AES key itself is encrypted with the game's public key. The store server has the private game key and can decrypt it.

The receipt list is encrypted twice: Data themselves are encrypted with the public gamer key, and the console has the private key to decrypt it. The second "inner" encryption was made with the private developer/game key, and the in-game public key is used to decrypt it (that is reverse to what private and public keys are normally used, but it works somehow).

The problem for my own OUYA server is that I do not possess the private keys of all of the 1200 games. I am not able to decrypt the purchase requests, and not able to encrypt the receipt lists in a way that the game can decrypt them.

Patching

I might not able to break purchasing, but I could unlock the individual games.

Bloo Kid 2

Bloo Kid 2 was the first game I tried. I already knew where to look for and modified isFullVersion in the smali code to always return true. Then I re-compiled the smali code, created the .apk and signed it.

But now I had the problem that my signature was different from the original signature, and Android refused to install the new game version over the old one. Even uninstalling the game while keeping the data (pm uninstall -k) did not work; Android refused to install the modified game with the new signature as long as it had data for the original signature :/

This would mean that people would lose all their progress and settings.

Comment by Jesús Higueras: At least on regular Android devices you can run pm uninstall -k and then restart the device. The data will still be there but the signature will no longer be cached, so you'll be able to install your modified version on top without losing data.

Babylonian Twins

Each game has a "purchase success" and an equal "purchase failure" handler. My idea was that upon game start, I would call the purchase success code with the right product key and be done with it.

I did that, and the game crashed - inside the C code that I could not inspect so easily. I even installed Ghidra to be able to look into the C code.

It took me three evenings to get the game to the point that it actually worked. The solution was to call the purchase success method from the purchase failure handler :) When calling it directly on startup, some things in the C code were not yet initialized and that caused the crash.

Here again I faced the impossible-to-upgrade and the keep-my-savegames problem.

I stopped the work because patching individual games does not scale.

Nocryption

The OUYA saviors very early had the idea to use Xposed to overwrite OuyaEncryptionHelper methods and prevent them from doing any encryption/decryption.

Unfortunately this helper is compiled into every application, and more often than not obfuscated with methods names like a.a.a.d.c. We would need a list that mapped that class name for each of the games, which would mean a lot of manual work.

While reading through the decompiled OUYA Framework source code and the game-specific implementations I saw something:

Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
instance.init(2, secretKeySpec, ivParameterSpec);
receipts = new JSONObject(new String(instance.doFinal(decode), "UTF-8"));

The framework and every game used Java's Cipher.doFinal(byte[] input) to actually execute the encryption or decryption operation. If it was possible to let doFinal() return the input bytes unmodified, then my server would get plain text purchase requests and could return plain text receipts!

It took a whole day of installing Xposed on the OUYA, learning how to build an Xposed module and write a pre-execution hook for Cipher.doFinal. The actual code is disappointing:

XposedHelpers.findAndHookMethod(Cipher.class, "doFinal", byte[].class, new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) {
        byte[] input = (byte[]) param.args[0];
        param.setResult(input);
    }
});

The result was this:

Bloo Kid 2: Main menu Bloo Kid 2: Purchase confirmation Bloo Kid 2: Thanks for supporting winterworks GmbH! "Purchased" in OUYA's system menu

And all this without modifying the game apk itself! The savegames are safe!

How to use

Using my "plain-purchases" xposed module currently takes a bit of work:

  1. Follow the Root the OUYA instructions until you have the following:

    1. SuperSU (required by Xposed installer)
    2. Xposed installer with activated Xposed framework

    (Both are also available in Make -> Tutorials).

    Do not install ModCollection nor Google Play (they are not needed, but do no harm when installed).

  2. Download and install my ouya-plain-purchases xposed module. (Also available in Make -> Tutorials).
  3. Activate it in the Xposed installer by going to "Modules" and then use the touchpad to click the checkbox that activates the plain purchases module. The controller buttons cannot be used to activate the module.
  4. Reboot
  5. Configure your OUYA to use my server or Devin's server.

Xposed module "OUYA plain purchases"

You may now enjoy all your OUYA games in their full glory again, half a year after the official server shutdown.

To unlock Final Fantasy III, the module needs to be installed as a system app.

Install xposed and the plain purchases module onto internal storage and not onto USB.

Takedown!

If you are a game developer and do not want your game unlocked on the couple of dozen OUYAs that are left on the world, send me an e-mail and I will remove the purchase receipt from my server. The game will then fall back to demo mode on all OUYAs.

Also on: Twitter, ouya.world.

Published on 2020-01-04 in ,


Debian: Moving to a larger harddisk

My Purism 13 v3 laptop that I bought and setup in 2018 has a 500 GB SSD inside. During installation, I had chosen to size the boot partition 256 MiB and a root partition 30 GiB. Both had proven to be the wrong choices, given that a kernel initramfs is ~80 MiB now, and the root partition was constantly filled to ~90%.

The rest of the disk was the home partition, which also began to feel crowded - I archive games of the PlayJam GameStick, have kernel sources lying around and extract many firmwares and software packages for reverse engineering purposes.

It was time to get a larger harddisk.

Interlude: Smaller kernels

I had solved the /boot size problem with the help of a forum post and configured initramfs to only include needed kernel modules:

/etc/initramfs-tools/conf.d/cw-modules
# dateien waren zu groß
# https://debianforum.de/forum/viewtopic.php?p=1367229
MODULES=dep

After running update-initramfs -u, the initrd.img-6.x.x-amd64 files have only ~26 MiB instead of the previous ~80.

Choosing a harddisk

I wanted to go and buy a M.2 SSD, but when searching for the answer to "M.2 with NVMe or SATA connector", I saw that the laptop shipped with a 2.5" SATA SSD.

I wondered a bit why it wasn't a M.2 disk, but went to a shop and bought a 2TB SATA SSD. Only when opening up the laptop case and exchanging the drives, I saw that the Librem v3 indeed has a M.2 slot :(

I could have had nearly 10x the speeds of the ~530MiB/s SATA disks, - had I only looked at the whole image in the wiki instead of just focusing on the SATA disk.

Preparations

I downloaded the Debian netinstall image and copied it to a USB flash drive: sudo cp debian-12.7.0-amd64-netinst.iso /dev/sdb.

Then I put the new disk into the laptop, plugged in the flash drive and powered it on and waited until I saw the boot menu.

Now I connected a the old disk with a new SATA-to-USB3-adapter to the laptop and started the graphical rescue application.

The rescue mode worked, but required me to go back to the main menu several times to re-read the partition table, which I did not manage to do via the shell.

0: Copy old to new disk

Copying the old disk to the new one was easily done with a standard cp command:

$ cp /dev/sdc /dev/sda

sda was the new disk, sdb the flash drive and sdc the old disk I connected via USB.

1: Moving and increasing partition size

My system is encrypted, and so I had to move and resize several things.

SSD harddisk MBR partitions
Number Size Device Usage Task
1 256 MiB /dev/sda1 boot Increase to 2 GiB
2 487 GiB /dev/sda2 extended Move by 1.75 GiB; increase to 1951 GiB
5 487 GiB /dev/sda5 encrypted data Increase to 1951 GiB

To be able to increase the boot partition, I had to make space and move the extended partition further behind by 1.75 GiB.

Moving the second partition was possible with sfdisk (2048 - 256 = 1792):

$ echo '+1792M,' | sfdisk --move-data /dev/sda 2

It had a speed of ~200MiB/s.

After that I increased sizes of boot and extended partitions with parted:

$ parted
(parted) resizepart 1 2048MB
...
(parted) resizepart 2 2000GB
...
(parted) resizepart 5 2000GB

Then I had to leave the shell and use the rescue menu's "read harddisk" to reload the partition table.

2: Encrypted disk

The actual data on the laptop are encrypted, and so I first had to increase the /dev/mapper/crypted_sda5 partition, which I again did with parted.

3: LVM

The encrypted partition contains a LVM "volume group", which can be inspected with vgdisplay. I think it already had the correct size.

Inside the volume group are LVM "logical volumes", the root, swap and home partitions (lvdisplay). I simply used lvresize to change their sizes - 200GiB for root, and the rest of the 2 TB to the home partition.

For some reasons the lvresize -s option did not work and I used resizefs on each of the partitions afterwards, which required me to e2fsck manually.

4: Booting

After all the partitions were resized, I ran grub-install /dev/sda. Unfortunately booting did not work, I only saw a UEFI tool coreinfo 0.1 with CPU and RAM information.

This was solved by running update-grub, which installed the boot manager into the UEFI instead of the MBR.

Published on 2024-11-03 in


GameStick server improvements

I've been continuing work on the Playjam GameStick replacement server and managed to implement two features: Achievements and leaderboards.

Achievements

Achievements are things you reached in a game, e.g. jumping onto 3 enemies without touching the ground in Bloo Kid 1, a "Triple hit".

Whenever a player unlocks an achievement, the game sends that information to the server - a simple set-complete API call with the ID of the achievement and the session ID of the user. The server then has to store it in a database, and return it when requested.

Achievements can be requested in several locations:

  • The GameStick's main user interface lets you look at a list of all games you have achievements for.
  • The achievement overview lets you inspect all achievements for each game in detail.
  • Games fetch the list of achievements when they start, and want the information if the user has unlocked them already. This is helpful when you have multiple GameSticks and want to continue where you left off on the other stick.

GameStick UI: profile GameStick UI: Games with achievements GameStick UI: Game achievement details

Each of those locations had its own API call. Implementing the first and the last were easy, but the per-game achievement overview in the GameStick UI was pretty hard - the JSON structure was not parsed by the low-level GameStick database interface, but was handed off unparsed to the Marmalade-based UI, which parsed it and then crashed.

Guessing the property name for the achievement list yielded no results. Since the Marmalade SDk compiled code into C, it is pretty hard to decompile.

Fortunately, I found a Ghidra plugin for Marmalade, and after fixing a bug I could load the Console.s3e file into Ghidra. After some looking I found the property name for the achievement list: gameList.

Ghidra: Console.s3e analysis

All achievements were unfortunately registered at the server, and we have no backups of them. This means the games have to be decompiled and inspected to find out which achievements exists, what their title and description is. The achievements on the server also had images, which are lost forever.

Currently the game metadata repository has achievement information for Bloo Kid, Boulder Dash, Prince of Persia and ShaqDown.

Achievements in Bloo Kid Achievements in Prince of Persia

Leaderboards

Unlike the OUYA, the GameStick had native support for high score lists (but only one per game).

After implementing achievements, leaderboards were quickly done - the only API calls required for all games I know are saving the current score, and fetching the top 50 scores.

A couple of games provide high score lists: FallingBird, FourColorTaxi, GridLock, Magnetoid and Prince of Persia.

Stop the birds unfortunately fails to show the leaderboard for yet unknown reasons.

Leaderboard: 4 color taxi Leaderboard: Falling bird Leaderboard: Gridlock Leaderboard: Magnetoid Leaderboard: Prince of Persia

Also on: Mastodon, Twitter.

Published on 2024-10-30 in


Kündigung LVZ Zeitungsabo

Unser Haushalt hat die lokale Tageszeitung LVZ seit mindestens 50 Jahren abonniert. Vor einigen Wochen haben wir das Abo gekündigt.

Ein Punkt - wenn auch der kleinere - war, daß die Zeitung vormittags zu unregelmäßigen Zeiten zugestellt wurde. Ich lese die Zeitung beim Frühstück um 6:45, und in den letzten Jahren verschob sich die Zustellung von 6:30 auf 7:00, 7:30, 8:30. Manches Wochenende kam sie sogar erst Mittag, viel zu spät selbst für das späte Frühstück am Wochenende.

Der zweite aber viel schwerwiegendere Punkt war die abermalige Verringerung des lokalen Zeitungsinhalts. Ursprünglich gab es einen Wurzener Lokalteil, der schon vor sehr vielen Jahren mit dem Grimmaer Teil zusammengelegt wurde und seitdem der Muldentaler Lokalteil war.

Nun sollten offenbar noch mehr Redakteure gespart werden, und der Muldentaler Lokalteil wurde zum Lokalteil des ganzen Landkreises "Leipziger Land" zusammengefasst. Dadurch bekamen wir Nachrichten aus Ecken des Kreises, die uns überhaupt nicht interessieren - aber viel weniger Nachrichten aus unserem Umkreis.

Wir haben das LVZ-Tageszeitungsabo deshalb gekündigt, aber ein neues für Freitag+Sonnabend abgeschlossen (hauptsächlich der Todesanzeigen wegen).

Inzwischen probiere ich mich durch die überregionalen Wochenzeitungen durch, denn die haben zwar genausowenig regionale Nachrichten wie die LVZ, dafür aber besser und tiefer recherchierte überregionale Nachrichten.

Published on 2024-10-27 in


SSH-Verbindung bricht nach 5 Minuten ab

5 Jahre nach Projektstart haben wir endlich einen Glasfaseranschluss bekommen, und zwar mit der enviaTel als ISP.

Kurz nach der Umstellung bemerkte ich, daß SSH-Verbindungen nach einiger Zeit unterbrochen wurden und hängen blieben - anfangs konnte man sie normal benutzen, aber dann reagierten sie nicht mehr auf Eingaben.

FritzBox?

Als Router bekamen wir eine Fritzbox 5530 Fiber, und diese machte ich auch erstmal für das Problem verantwortlich.

AVM selbst dokumentiert, daß FritzBoxen Verbindungen nach 15 Minuten trennen.

Weiterhin gibt es im Netz viele Berichte über dieses Problem, allerdings sind die meisten schon mehr als 15 Jahre alt:

Bevor ich mit an den enviaTel-Support wandte, wollte ich aber erstmal genauere Daten haben.

Problemeingrenzung

Der Blogpost My ISP Is Killing My Idle SSH Sessions. Yours Might Be Too. verwies auf das Tool NAT-TCP-test, dessen Server aber leider nicht mehr läuft. Das Kompilieren des Codes auf dem Server war mir zu aufwändig, weshalb ich eine Alternative suchte.

Zuerst probierte ich es manuell, schwenkte dann aber schnell auf einen Automatismus um:

$ date && ssh cweiske.de "sleep 240; date"
Fr 20. Sep 10:25:30 CEST 2024
Fr 20. Sep 10:29:30 CEST 2024

Die aktuelle Zeit wird ausgegeben, dann die SSH-Verbindung aufgebaut, einige Sekunden gewartet und die Zeit auf dem Server ausgegeben. Wenn die Verbindung zwischendurch gekillt wird, wird das 2. Datum nicht zu sehen sein.

Den Befehl startete ich mit unterschiedlichen Wartezeiten in mehreren Shells und bekam 300 Sekunden - exakt 5 Minuten - als Grenze heraus. 300 Sekunden waren ok, 301 nicht mehr.

Da die FritzBox untätige Verbindungen erst nach 15 Minuten trennt, konnte ich sie als Problemursache ausschließen.

Andere Probleme

Nicht nur SSH machte Probleme, sondern auch andere Programme zeigten Fehlverhalten:

Das native E-mailprogramm auf dem DM7080HD-Satellitenreceiver blieb regelmäßig hängen und konnte auch mit dem sonst üblicherweise funktionierendem Programm-verlassen-und-wieder-reingehen keine neuen Mails empfangen.

Im Android-Emailprogramm K-9 Mail konnte ich keine neuen E-Mails mehr abholen bis ich es hart beendet und neu gestartet hatte.

Das Android-Synchronisationstool DAVx5 zeigte Fehlermeldungen an:

Weicher Fehler (maximale Anzahl an Wiederholungen erreicht)

Carrier-Grade NAT

Ich schilderte dem enviaTel-Support mein Problem. Der erläuterte, daß mein Internetanschluss Teil eines Carrier-Grade NAT (CG-NAT) ist. Das bedeutet, daß der Router keine echte öffentliche IPv4-Adresse bekommt, sondern nur eine enviaTel-interne. Beim Test mit ip.cweiske.de bekam ich die 212.99.194.23 als öffentliche IP angezeigt, während die FritzBox die externe IP 100.65.240.42 hatte.

CG-NAT verhindert zum einen Verbindungen aus dem Internet auf den Router (VPN ade) - und zum anderen heißt CG-NAT, daß es noch eine 2. Routerebene gibt, die IP-Verbindungen von der enviaTel-internen IP auf die öffentliche mappt.

Der Support bot mir an, zu Diagnosezwecken auf eine echte öffentliche IPv4-Adresse umzustellen. Sobald das getan war, waren die Verbindungsprobleme weg - SSH-Verbindungen blieben sogar mehr als eine Stunde offen.

Eine FritzBox im Jahr 2024 tötet inaktive SSH-Verbindungen also nicht nach 15 Minuten. Das früher mal existierende Problem existiert nicht mehr.

Ich verwies den Support auf Abschnitt 5 der RFC 5382:

REQ-5: If a NAT cannot determine whether the endpoints of a TCP connection are active, it MAY abandon the session if it has been idle for some time. In such cases, the value of the "established connection idle-timeout" MUST NOT be less than 2 hours 4 minutes. The value of the "transitory connection idle-timeout" MUST NOT be less than 4 minutes.

Das ist genau dasselbe Problem, das ato im oben verlinkten Blogbeitrag hatte.

Zum Schluß habe ich mich mit der enviaTel darauf geeinigt, daß ich die echte IPv4-Adresse kostenlos bekomme bis das Problem behoben ist (statt üblicherweise 3€/Monat).


weitere Stichwörter: NAT-Keep-Alive

Published on 2024-10-22 in


Bester Notenfeed

Das Wurzener Gymnasium nutzt beste.schule als digitales Notenbuch. Dankenswerterweise bietet die Plattform eine dokumentierte API und im Benutzerbereich die Möglichkeit, ein Zugriffstoken zu erstellen.

Damit kann man dann nutzen um sich z.B. die Noten der Kinder direkt im Feedreader anzeigen zu lassen:

Bester Notenfeed im Feedreader FreshRSS

Der Code des Besten Notenfeed liegt auf meinem git-Server und auf Codeberg.

Auch auf: Mastodon.

Published on 2024-09-17 in


"Waschmaschine fertig"-Benachrichtigungen

Immer mal wieder sind Waschmaschine oder Trockner fertig, aber wir vergessen danach zu schauen und nehmen die Wäsche dann zu spät raus. Idealerweise würde uns das Gerät Bescheid sagen wenn es fertig ist, aber bei uns sind weder Waschmaschine noch Trockner mit Netzwerkfähigkeit ausgestattet.

Nachdem ich den Shelly Plug S in den Volkszähler eingebunden hatte kam ich auf die Idee, daß ein Script einfach regelmäßig prüfen könnte, ob das Gerät in den letzten X Minuten Strom verbraucht hat und jetzt keinen mehr verbraucht. Das wäre das Signal, daß es fertig ist und der Trigger zum Verschicken der Benachrichtigung.

Das Ergebnis ist das Script washing-finished, welches auf meinem Git-Server und auf Codeberg zu finden ist.

Ich lasse es alle 5 Minuten per cron laufen; es prüft den Stromverbrauch der letzten 20 Minuten. Wenn der über 5 Watt war und jetzt unter 5 Watt liegt, dann beendet sich das Script mit Exitcode 0. Dadurch kann man es mit && mit dem nächsten Script verknüpfen:

*/5 * * * *  /usr/local/src/washing-finished-vz/washing-finished.php 03baab00-6d01-11ef-829a-7bbe8571431b && xmppc -m message chat cweiske@cweiske.de "Waschmaschine fertig"

Das Handy zeigt das ganze dann an:

Benachrichtigung auf dem Handy

Auch auf: Mastodon.

Published on 2024-09-15 in


86.7% of all GameStick games archived

This week the GameStick Fans community reached a milestone: Of the 188 games that were once available for the PlayJam GameStick, 163 have been archived and only 25 remain missing. That's 86.7%; a huge success for the project that started in 2024-04.

Playjam GameStick user interface

Obtaining apks

The lack of a GameStick community meant that nobody had backed up the available games before PlayJam announced the micro console's death in 2017.

After collecting cache files from GameSticks, 37 games could be downloaded from their AWS S3 URLs without authentication.

Another way to get the games is to contact the developers, but we had only limited success in getting answers to our e-mails, let alone .apk that the developers often did not have anymore.

Extracting installed games from existing GameSticks was the third option to get the game .apk files into our hands. It took a while and quite some effort until we were able to copy files off GameSticks - the devices were locked down and allowed neither file access nor sideloading.

The following people helped by copying games off their GameSticks or contacting developers to get .apks directly from them:

  • Alex Shorts
  • Andyweli
  • cweiske
  • eyehorus (Wayne)
  • jrronimo
  • Kazdan R. Azahn (KRAZ Pro.)
  • Lee Chapman
  • Ryo
  • Toast (Liam)

Missing games

We do not yet have the .apk files for the following GameStick games:

# Game name Developer
1Animalz: Super SquadInvaderGames
2Baa Baa BomberRatJar Games
3Beast Boxing TurboGoodHustle
4Canabalt HDKittehface Software
5CR FootySacred Duck Games
6Crumble ZoneRebel Twins
7CUBISTICPytebyte
8Dead RushingDancing Cat Development
9Dr. Bulbaceous Puzzle SolverWetgenes
10EDGEMobigame
11Finger Hoola+Plantpot
12Furfur and NubloDevilishGames
13Fuz RushJet Stone Studios
14Groundskeeper 2OrangePixel
15Nimble QuestNimblebit
16OrborunTiny Lab Productions
17Pix'N Love RushAdvanced Mobile Applications
18REDKnife Media
19Sci-FightersHeadup Games
20ShaqDownOne Spear
21SpaceCatPlatty Soft
22Space GruntsOrangePixel
23Space Vermin GameDisciples
24Usagi YojimboHappy Giant
25Wasteland Bar FightKybernesis

If you have a GameStick with one or more of this games - follow the apk rescue instructions, or ask for help in chat.

Archived games

The following games have been backed up and archived in the GameStick library at the Internet Archive.

Recent gameplay videos can be found on Wayne’s Gaming Den "PlayJam GameStick" playlist

Game name Developer
100 RoguesFusion Reactions
4-Color TaxiShadowBrain Games
Alien SpindomeAGameAWeek
Alpha CollexionAGameAWeek
Alpha WaveHardline Studios
Animal BattleOvogame
Animal Math Games for KidsEggroll Games
AquaVentureJellyfish Games
Asteroid 2012M-Spacemedia
Back to BedBedtime Digital Games
Battlebow - Shoot the Demons HDHaybyte Studios
Beasts BattleBadim
Beat DriftLunarPixelGames
Beta CollexionAGameAWeek
BionflytheOmenbit
BlastTraxAGameAWeek
Blockman Gets HungryAGameAWeek
Blockman WorldsAGameAWeek
Bloo Kidwinterworks GmbH
Boson XIan Mclarty
Boulder Dash - The FULL CollectionFirst Star Software
BounceABallAGameAWeek
BoxsplodingsAGameAWeek
Brave WarriorsGeorgeGames
Catcha Catcha AliensHotsauce Interactive
ChuckABallAGameAWeek
codestormPOLYGAMe d i g i t a l
Cro-Mag Rallycitizen12 studio
Crypt of BaconthulhuHamster Republic Productions
Cube DefenderPark Posse Games
Cyberflow Larissa Davidova
Dark IncursionBig Blue Bubble
D-GLESKokak
Disc BlasterAGameAWeek
Diversionezone.com
Epic Eric232 Studios Ltd
EvacHexage
Every Good Bird Deserves FlappynessAGameAWeek
Expendable RearmedRetrobomb
ExperimentHeadup Games
ExZeus 2HyperDevbox Japan
Falling BirdOne Legged Seagull
Final Freeway 2ROyatsukai Games
Fishy RushSilenGames
Fist of Awesomeifightbears
FlappynougImogia Games
Fly to the Moon!Badim
Fox Tales: The Skeleton KingWon-O-Soft
Freeeshrealtech VR
FutrinoAGameAWeek
Future Classics - 1986 EditionAGameAWeek
Galaga Special EditionNamco Bandai
GameStick Media PlayerPlayJam
Gene Effect Lightstorm3D GmbH
Genius Greedy MouseSpace Fractal
Greenie's Little Adventure AGameAWeek
GridlockLudometrics
GunslugsOrangePixel
Harpoons and BallsAGameAWeek
Haunt the House: TerrortownSFB Games
Haymaker Team Haymaker
Hazard RushSurreal Street
Heroes of LootOrangePixel
Hero of ManyTrickster Arts
Hoppy BobbyAGameAWeek
Icy Towerfreelunchdesign
INDY500® Arcade RacingHyperDevbox Japan
Invisible Munky 2AGameAWeek
It Takes TwoEpiphany
Karl's Tiny AdventureAGameAWeek
Knightmare TowerJuicy Beast
Kosmik Revengerealtech VR
LoveThe Bancast
LvLnTanuki Entertainment, Inc.
Magic RampageAsantee Games
MAGNETOID HD - Robo RunnerRoom 247 Studio
Maldita Castilla Locomalito
Metaloid Reactor Guardian Retro Revolution Games
MIMPISilicon Jelly
MiniSquadronAdvanced Mobile Applications
MiseriaRatJar Games
Momonga Pinball AdventuresPaladin Studios
MotorbikebaKno Games
Muffin KnightAngry Mob Games
Munky Blocks dxAGameAWeek
My Little Rescue HelicopterAGameAWeek
NeonPlat ExtremeAGameAWeek
NeonPlat's Cosmic AdventureAGameAWeek
Night Riders, 3D Arcade Racingbaka-neko.fr
No Gravityrealtech VR
Oddy Smog's Misadventure Play Medusa
OverdroyRoberto T. Fauri
PAC-MAN + TournamentsNamco Bandai
Panic FlightAdvanced Mobile Applications
Paragon InfiniteBipolarDesign
Particle Arcade ShooterDianox Games
Penguin ParadeAGameAWeek
Platdude in A Bit of A DoAGameAWeek
Platdude in A Bit of a SpinAGameAWeek
Platdude in A TubeAGameAWeek
Platdude in Battling OstrichesAGameAWeek
Platdude in SpaceAGameAWeek
Platdude in Swamp GolfAGameAWeek
Platdude in The Spirit CollectorAGameAWeek
PoperlyAGameAWeek
Prince of Persia: The Shadow and the FlameUbisoft
Puzzle 2 HDAdvanced Mobile Applications
Quantic PinballShineResearch
RadiantHexage
Raiden LegacyDotemu
Reckless GetawayPixelbite
RepulzePixelbite
RetroBallAGameAWeek
RetroidTaurris
Retro RacingJamie Woodhouse
RiptideVector Unit
Royal OffenseBadim
Saturday Morning RPGMighty Rabbit Studios
Sela The Space PirateFeili Chen
Shadow BladeDead Mage
ShadowGunMadfinger
Sheep in Helldavid.cm
Shuttle Quest 2kTACSGames
Ski SafariDefiant Development
Sky Gamblers: Rise of GloryNamco Bandai
Sky HeroKokonut Studio
Slingshot RacingSnowbolt
Smash CopsHutch
SoccertronForgotten Systems
Soldier NightmareGeorgeGames
Soul PowerGhost Time Games
Sound RideOutOfTheBit Ltd
Space Ax'RIOT digital
SpaceBallGeorgeGames
Space Clusters DXAGameAWeek
Space Station A6-100AGameAWeek
SpikeDislike2AGameAWeek
SpikeDislike3AGameAWeek
Spiky TowerAGameAWeek
StalagflightStalagflight Team
Stop The BirdsHaybyte Studios
Stop The BotsScary Robot Productions
Super Grid RunRefresh Creations
Super Mega WormDeceased Pixel
Tales of Illyria 1Little Killerz
Tales of Illyria 2 - Beyond the Iron WallLittle Killerz
Tales of PocoroCurved Cat Games
The Button AffairModern Dream
The Chronicles of PandoraSuprnova Entertainment
The Other BrothersSimian Squared
The Tribloos 2Bumpkin Brothers
Tim-Tim "The Mighty Gnome"" Spoonweaver Studios
TOFU Media CenterPivos
Top Gear: Stunt School RevolutionBBC Games
Towelfight 2Butterscotch Shenanigans
TURBINEGeorgeGames
TurboFly HDOsaris Games
Twin RobotsThinice
unwindJijjy
VectorNekki
Vendetta Guild Software
Zombie Kill of the WeekPanic Art Studios
Zzap LinesAGameAWeek

Also on: Mastodon, Twitter.

Published on 2024-09-08 in


Volkszähler: Absoluten Zählerstand anzeigen

Ich bin bei meinem Stromanbieter Naturstrom auf einen flexiblen Tarif umgestiegen und muss deshalb jeden Monat den aktuellen Zählerstand melden, weil der kWh-Preis jeden Monat ein anderer ist.

Den Stromverbrauch erfasse ich bereits mit dem Volkszähler, d.h. Daten vom Stromzähler werden über einen USB-Infrarot-Lesekopf mit vzlogger ausgelesen, der die Daten an die middleware sendet, die sie in die Datenbank schreibt. Die Anzeige im Browser erledigt das Frontend.

Das Frontend zeigt allerdings nur relative Werte wie den aktuellen Verbrauch in Watt oder den Tagesverbrauch in kWh. Der SML-Zähler (Iskra MT175) liefert den absoluten Wert (1-0:1.8.0), und dieser steht auch in der Datenbank drin:

SELECT FROM_UNIXTIME(timestamp / 1000) AS `date`, `timestamp`, `value`
FROM `data` WHERE `channel_id`=2
ORDER BY `timestamp` DESC LIMIT 20
date timestamp value
2024-08-31 15:55:53 1725112553327 38402731.4
2024-08-31 15:50:52 1725112252638 38402711
2024-08-31 15:45:53 1725111953496 38402691.3

Im Wiki steht dazu:

In der Tabelle unter dem Graphen wird der absolute Zählerstand mit angezeigt wenn in den Kanaleigenschaften der Initialwert eingetragen ist (Zählerstand bei Inbetriebnahme des Volkszähler).

Ich habe den Kanal bearbeitet und bei "Initialverbrauch" eine 0 eingetragen. Nach 5 Minuten wurde mir der Gesamtverbrauch angezeigt, der allerdings zu niedrig war.

Nachdem ich die Differenz von aktuellem zu angezeigten Wert eingetragen hatte (8241) bekam ich den richtigen Wert angezeigt:

Volkszähler-Frontend mit aktuellem Zählerstand

Published on 2024-08-31 in