The latest posts in full-text for feed readers.
2013 PlayJam GameStick devices have a all-around rubber coating, and the controllers have a rubberized bottom. After 11 years, the coating's chemicals dissolved, leaving a sticky mess that sticks to your fingers when touching it.
It is possible to get rid of the coating by rubbing it off with Isopropanol (Isopropyl alcohol), an old sock and a pair of surgical gloves.
The rubber coating was mentioned in the 2013-07-11 Kickstarter Update #36: The Golden Sample:
- Put a rubberized coating on the bottom of the controller. This gives the finish a really nice quality feel.
- Used a rubber coating on the GameStick so that it did not scratch when inserted in to the controller. It really looks and feels fantastic as a result.
Published on 2024-12-31 in gamestick, hardware
I wanted to copy some movies to an external disk in preparation for our summer vacation, and attached an external USB3 disk to our Dreambox satellite receiver.
Nothing happened; the disk did not get automatically mounted. Manually mounting also failed. dmesg told me:
usb 10-2: new SuperSpeed USB device number 2 using xhci_hcd scsi2 : usb-storage 10-2:1.0 scsi 2:0:0:0: Direct-Access TOSHIBA External USB 3.0 0 PQ: 0 ANSI: 6 sd 2:0:0:0: [sdb] 1953525168 512-byte logical blocks: (1.00 TB/931 GiB) sd 2:0:0:0: [sdb] Write Protect is off sd 2:0:0:0: [sdb] Mode Sense: 43 00 00 00 sd 2:0:0:0: [sdb] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA sdb: sdb1 sd 2:0:0:0: [sdb] Attached SCSI disk EXT4-fs (sdb1): couldn't mount RDWR because of unsupported optional features (400)
I formatted the disk on a Debian unstable (kernel 4.19), and it enabled the metadata checksum feature that is not supported by the DreamOS 2.6's kernel (3.4-4.0-dm7080). I had to disable that feature on the disk with my laptop:
$ e2fsck -f /dev/sdb1 $ tune2fs -O ^metadata_csum /dev/sdb1
Source: Couldn't mount RDWR because of unsupported optional features (400)
The next mount try also resulted in an error:
usb 10-2: new SuperSpeed USB device number 3 using xhci_hcd scsi3 : usb-storage 10-2:1.0 scsi 3:0:0:0: Direct-Access TOSHIBA External USB 3.0 0 PQ: 0 ANSI: 6 sd 3:0:0:0: [sdb] 1953525168 512-byte logical blocks: (1.00 TB/931 GiB) sd 3:0:0:0: [sdb] Write Protect is off sd 3:0:0:0: [sdb] Mode Sense: 43 00 00 00 sd 3:0:0:0: [sdb] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA sdb: sdb1 sd 3:0:0:0: [sdb] Attached SCSI disk JBD2: Unrecognised features on journal EXT4-fs (sdb1): error loading journal
So kernel 4.19 also adds new journal data.
$ tune2fs -l /dev/sdb1 tune2fs 1.43-WIP (18-May-2015) Filesystem volume name: videos Last mounted on: /media/cweiske/videos Filesystem UUID: 7e07e565-99c7-4ea9-b2a3-1eb02ba23572 Filesystem magic number: 0xEF53 Filesystem revision #: 1 (dynamic) Filesystem features: has_journal ext_attr resize_inode dir_index filetype extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize Filesystem flags: signed_directory_hash Default mount options: user_xattr acl Filesystem state: clean Errors behavior: Continue Filesystem OS type: Linux Inode count: 61054976 Block count: 244190208 Reserved block count: 12209510 Free blocks: 210555405 Free inodes: 61042713 First block: 0 Block size: 4096 Fragment size: 4096 Reserved GDT blocks: 965 Blocks per group: 32768 Fragments per group: 32768 Inodes per group: 8192 Inode blocks per group: 512 Flex block group size: 16 Filesystem created: Fri Feb 15 20:05:55 2019 Last mount time: Sat Jul 13 10:51:53 2019 Last write time: Sat Jul 13 11:02:12 2019 Mount count: 0 Maximum mount count: -1 Last checked: Sat Jul 13 11:02:12 2019 Check interval: 0 (<none>) Lifetime writes: 121 GB Reserved blocks uid: 0 (user root) Reserved blocks gid: 0 (group root) First inode: 11 Inode size: 256 Required extra isize: 32 Desired extra isize: 32 Journal inode: 8 Default directory hash: half_md4 Directory Hash Seed: 83196fde-051e-43c5-967c-7aa3935c571e Journal backup: inode blocks
Instead of finding out which journal feature I had to disable, I disabled the whole journal:
$ tune2fs -O ^has_journal /dev/sdb1
I could now finally mount the disk on the Dreambox.
Source: Debian User Forums: Boot failing: No init found
Tom told me how to reactivate the journal:
$ tune2fs -O has_journal /dev/sdb1
Published on 2019-08-06 in dreambox, linux
The PlayJam GameStick had native support for using mobile phones as game controllers via its Companion app. Youtube still has a the official demo video showing it' usage.
When starting the app, it would show the GameStick and you could connect to it. Then the GameStick would deliver a branded controller skin to the app, which then displayed a custom controller interface for the game (or the standard one if the game had no own).
While looking through the files of a GameStick backup, I found some of the cached files in the /data/com.playjam.ca.service/files/ directory: .bundle and .meta files. The .bundle files are just Zip archives that contain an XML configuration file, background, button and stick images.
I took the time and built some little scripts to convert the XML configuration file via XSLT into a SVG image, and a script to embed the bitmap image files into the svg file. They can be found in the companion-app repository on Codeberg.
Published on 2024-12-15 in gamestick
Änderungen für Echtzeitüberweisungen
Ab dem 9. Januar 2025 gelten neue gesetzliche Vorgaben der Europäischen Union für Echtzeitüberweisungen.
Auf die Berechnung eines separaten Entgeltes für die in Euro ausgeführte Echtzeitüberweisung von bisher 0,90 Euro wird verzichtet. Die Entgelthöhe entspricht der Höhe einer Standard-Überweisung.
Ferner reduziert sich die Ausführungsfrist für Echtzeitüberweisungen von maximal 20 Sekunden auf maximal 10 Sekunden.
Volksbank Muldental, 29.10.2024
Published on 2024-12-11 in bank
The OUYA was known for providing a custom input handling system, because the standard Android 4.1 one was not sufficient.
I never knew the exact cause, but yesterday I stumbled upon the archived PlayJam Games Network Controller Guidelines - it lists the bugs in detail:
Due to an overly simplistic implementation of the dpad translation code in Android, (up to and including Android 4.4), using onKeyDown() and onKeyUp() events with multiple controllers is not recommended. While onKey*() events work acceptably for single controllers, when multiple controllers are used it is possible for events from one controller to be misidentified as coming from another.
Android provides several different methods for identifying controllers, however they are not all equal in terms of functionality:
- InputDevice.getId()
- Guaranteed unique for the current power cycle
- Not stable – They are simple incrementing integers that are assigned to each device as it connects – even if it is a device that has already been connected – as such it is guaranteed that a different value will be returned for a controller before and after a power cycle of that controller, (i.e. if it disconnects, its getId() will change).
- InputDevice.getDescriptor()
- Stable – The value for any controllers will stay the same through power cycles of the controller and/or the platform – the value itself is derived from physical properties of the device.
- Not guaranteed unique – As the value is derived from a set of physical parameters of the device, it is possible for devices which share the same set of parameters to return the same value. This is generally not a problem for Bluetooth devices, as the MAC address of the Bluetooth client is part of the input to the id generator.
- InputDevice.getName()
- PlayJam provides managed naming of controllers – in that the controller with a specific player lit up will remain the same name – so the controller with light one on, (Player 1), will always return the device name of “GameStick Controller 1”. Note that the order of light assignment is currently related to the power up order of controllers – so turning off controllers and repowering in a different order will result in name changes – however the controller itself will still show the correct light for its name, (making the assignment clean to the user).
I guess that the OUYA developers implemented something like the GameStick people, which meant that all games that wanted to support multiple controllers had to use the OUYA SDK, or do all the work themselves.
Published on 2024-12-06 in android
My PlayJam GameStick replacement server is now able to deliver firmware updates to the GameSticks: Version 2049 to 2058, and 2058 to 2071.
The GameStick server was shut down in 2017, long before we tried to preserve the system. We thus could not download firmware images from the server, but GameStick Fans member Ryo found the firmware files for 2058 and 2071 somewhere on the internet and collected them for us.
Joe, a new GameStick Fan who joined in 2024-11, found the firmware image for 2049 on his stick.
Those full firmware images are ~180 MiB large and fully reset your GameStick - all settings and games are wiped. They can't be used for automatic updates.
After I manged to build a firmware update that enables the Android debug bridge, Ryo found an official firmware update on one of his GameSticks in 2023: Update-2058-to-2071.img. It was located in the /cache/ folder and only 3.2 MiB large.
Another stick contained Update-2049-to-2058.img (9.9 MiB).
Those .img files are small downloads and keep all the settings and games as they are - the right ones for automatic updates.
The initial request to check for firmware updates is pretty standard: A HTTP POST request to http://update.gamestickservices.net/check.php (actually 2 different domains and one hard-coded IP address), which returns a JSON structure that tells the stick if a firmware update is available, the textual changelog and if the stick must install it ("forced update") - and of course the download URL.
By reading the decompiled OOBE code, I figured out that the download URL gets modified and URL parameters are appended:
The firmware update is split into parts (chunks) of 100 kiB, and each chunk is "encrypted" by XORing it with the magic number 0x5b, and the binary SHA1 sum is put before the data itself.
My guess is that the developers did that to prevent people installing their own updates - a measure that was already defied in 2014-03, known as the lukepanic hack method. That hack supposedly (I don't have the files) opens a web server and delivers a custom firmware update :)
My server now contains a script that takes a firmware update, splits it into chunks, encrypts and signs them - so that the download files can be delivered statically.
All firmware images and updates known to us have been uploaded to the Internet Archive: 0.9.2049, 0.9.2059 and 0.9.2071.
Next we'd really like to have a developer firmware, and also know that there was a 0.9.2079 version we do not possess yet. Let's hope we'll find more sticks with cached firmware files on them.
Published on 2024-12-03 in gamestick
I wanted to watch Chaos Communication Congress lecture videos during lunch, but found no Android app that would store which talks I had already seen and the progress in each video.
I only found one open source video app in the F-Droid repository that allows extensions: Miru. So I decided to build an extension for the media.ccc.de service that hosts the congress videos.
It was hard to get Miru running on the desktop, and in the end I had to patch it to get it compile on my machine.
The extension documentation lacked quite a bit in depth and I had to find out many things on my own by looking at other extensions and miru's source code.
After a day of work, I submitted the media.ccc.de extension for inclusion into the official extension repository, and it got merged only a few hours later.
Miru's navigational structure (List > TV Series details > Play) did not really fit the media.ccc.de structure, and I had to compromise quite a bit - details of talks are non-existent when viewed from the events detail page. Searching for talks gives much more details, though - but does not let you filter for a certain congress.
When writing the patch that adds Miru to the list of apps, I saw that NewPipe already supports media.ccc.de - I just did never notice it!
Published on 2024-11-25 in android
Years ago I registered at deepgram.com while trying to transcribe a podcast episode, with username+deepgram.com@example.org as my e-mail address. Today I received spam mails to that email address.
I contacted their security@
mailbox and asked if they have
been hacked, but they denied that:
We apologize for the inconvenience you are experiencing. We can confirm that we did not experience a breach, but that data is shared in accordance with our privacy policy, and with the vendors specified in our subprocessors list https://deepgram.com/privacy/subprocessors.
So they know that their "subprocessors" are selling/losing/whatever user account data and are fully ok with it.
Published on 2024-11-25 in bigsuck
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.
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.
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).
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.
$ 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.
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.
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.
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 android
With a bit of setup work, you can again play all the games you bought on your OUYA gaming console.
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.
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.
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 :/
OUYA's own Purchasing documentation describes how purchases happen on the OUYA:
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:
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.
I might not able to break purchasing, but I could unlock the individual games.
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.
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.
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:
And all this without modifying the game apk itself! The savegames are safe!
Using my "plain-purchases" xposed module currently takes a bit of work:
Follow the Root the OUYA instructions until you have the following:
(Both are also available in Make -> Tutorials).
Do not install ModCollection nor Google Play (they are not needed, but do no harm when installed).
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.
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 android, ouya