Compiling ecwolf for the RG280V

2021-12-06

So I ended up picking up an RG280V last week and it arrived over the weekend. I'm very pleased that I decided to get it, and managed to work with it a fair amount over the weekend, including trying different retroarch emulators for it. I replaced the stock image with the Adam Image, which is designed for the RG350, but it runs the same chip as the RG280V. Retrogamecorps published a starter guide that got me off the ground without too much trouble.

The whole system is built on OpenDingux, and runs Retroarch targeting the framebuffer for emulation (no X or window management). The processor is a MIPS processor, so in theory I could take SDL 1.2/2.0 apps for Linux and recompile them and package as an opk for the system. This requires cross-compilation, which I'm not very familiar with.

The particular app I want to bring over is ECWolf: an enhanced source port of Wolfenstein. I really want to play through the original again, and doing it on the RG280V is a perfect fit. ECWolf has some compilation guidelines on the wiki, but it doesn't cover this case. I tried the precompiled binary in one of the app repos on GitHub, but when I launch Wolfenstein (ECWolf), it just crashes.

The actual link to the patch utility on the page for x86_64 is broken because he changed from bz2 compression to xz and forgot to update the links.

The internet thinks this is about my data files, but I patched my data files with the ECWolf utility for data file patching to bring them up to date, but still no joy. I also validated the checksums against the canonical checksums that each file should have over on the ecwolf wiki, which also did not help.

Anyway, the data files aren't the problem. I changed the RG280V to use a USB mode for linux that will mount it via cable as an ethernet device, which then allows me to SSH into it with username od and password untoqebboqvboqudrn. I'm putting that here for reference: it's documented on the Adam image page under "System Access". I'm using version 1.3.1 of the image, I believe, and this approach let's me scp files to the system rather than pulling out the cards constantly.

So I get shell access, and learn there is no package manager. I also find the opk files, and learn that I can run them with opkrun. This reveals the true error: ecwolf.opk was compiled with an optional dependency on libbz2 enabled, and the shared library can't be found on the Adam image. Since there's no package management, it's not easy to install, and I haven't been able to find a version online.

So my strategy is to set up cross compilation targeting the 1.0GHZ JZ4770 CPU in the unit. I found a guide that should get me started with this, but I will then need to apply that approach to the ecwolf source code and see if I can produce an opk with minimal system dependencies. ecwolf uses CMake, which I'm not super familiar with outside of compiling OpenMW from time to time, so this will be a fun learning experience!


A day passes...

After about ~6 hours of work, I did manage to build a modified MIPS version of ECWolf that runs on the RG280V! Very exciting moment, but there were lots of dead ends, and sorting through my thinking will be a challenge. I'll try to capture my thoughts here for later refinement.

  • Cross-compilation isn't as magic as I thought. The basic idea is to bootstrap yourself to produce a compiler (and dependencies) that target a different architecture. This is a manual process, and https://github.com/tonyjih/RG350_buildroot proved to be very useful. I ran make toolchain and then make sdl sdl_image <a bunch of other sdl packages>. I got the list of packages with make show-targets.
    • This process was broken because the server in France that the build attempts to pull isl-0.18 from wasn't responding. The file was an xz file, but I found that by updating the shasum in the package folder for isl and pointing the server to https://gcc.gnu.org/pub/gcc/infrastructure/ got me past this hitch, even though the GNU server uses bz2 for compression. I think CMake knows how to decompress these payloads transparently, since it Just Worked.
  • This outputs content to two folders: output/target and output/host. I initially used target for everything, but to make it all work, I ended up binding everything to output/host. This folder contains a sysroot that I can point the ecwolf build to for all its tooling: the C compiler, C++ compiler, ar tools, etc.
  • I installed Ubuntu's mipsel (this is little-endian 32-bit MIPS, I discovered) build-essential and tried using that, but it builds for Linux and links against libc, and ecwolf on the RG280V needs to use ld-uClibc.so.0, so using the toolchain built for the RG350 was necessary.
  • I looked into using Qemu and building there, but Qemu was tough to get running, and I don't have a development image for MIPS. I came across the idea in a SO post about cross-compilation. I tried booting into the adam image with qemu-system-mipsel adam_v1.3.1.img, but it never initialized the display, so I went back to the cross-compile approach.
  • To get ecwolf to use the toolchain, I ended up doing a bunch of magic:
    • Use cmake to create a native build. I.e. cd ecwolf && mkdir build && cd build && cmake ../ && make -j4. This outputs a file called ImportExecutables.cmake that needs to be referenced in the MIPS build. I figured this out from reading:
      • A thread on cross-compilation of ZDoom. The author of ecwolf, Blzut3, commented there and provided just enough info for me to power through.
      • Two critical commits on the funkey-s fork of ecwolf:
        • This commit shows how to update IMPORT_EXECUTABLES.
        • This commit has the opk creation script, as well as tweaks to file_directory.cpp to remove fts.h (which seems to be missing), and defines __sun, which turned out to be a variable I used to make the compilation work in the end. Specifically, I had problems with the complier not understanding the ifdefs in the file, so I removed all platforms except the path for __sun, which is how I ultimately got the build I used.
  • My initial compiles worked for MIPS (I used file to figure out what they were), but were linked against ld.so.0 rather than ld-uClibc.so.0. I was confused about how to bring in the correct libraries for linking, and ran across this discussion of specifying a sysroot on the ROS forums. This turned out to be a dead-end; my final solution did not require setting sysroot.
  • The final executable is dynamically linked. I read through CMake's docs on linking, but haven't figured out the approach here yet.
  • The root issue, which relates to ecwolf linking against libbz2, led me down a path of compiling libbz2 myself and dropping it into the Adam image. Because Adam uses squashfs for the root filesystem, I wasn't able to drop the MIPS so into the directory. And because all apps are run with opkrun (which mounts the opk on loopback on /opk to run it), I don't have control over LD_LIBRARY_PATH at runtime, and no non-system paths are in the equivalent of ld.so.conf (which OpenDingux doesn't seem to have, but there must be some equivalent, I'm guessing). I tried bundling the libbz2 into the opk directly, but this was not effective.
  • After completing the native build, and then created a mipsbuild directory under ecwolf and ran cmake: mkdir mipsbould && cd mipsbuild && cmake ../. This produces CMake's best guess about how to run the build, captured in a file called CMakeCache.txt. This file is where I updated all the compiler locations, disabled the extrenal bz2, and generally did a ton of surgery. Once that's set up, running make -j4 produced a build in about 30 seconds or so.
  • I learned a lot about squashfs as part of this. Especially how to unpack and repack opks. The blog I learned about most of this from has a link to a small project the author built that has a target for building the opk as well.

Briefly:

cp /mnt/clonezilla/live/filesystem.squashfs /path/to/workdir
cd /path/to/workdir
unsquashfs filesystem.squashfs
mv filesystem.squashfs /path/to/backup/
cd /path/to/workdir
mksquashfs squashfs-root filesystem.squashfs -b 1024k -comp xz -Xbcj x86 -e boot

I didn't use that last line because I wasn't sure what the options did...they seem to be related to bootability, which I don't care about. Instead used a script from a fork of ecwolf:

mksquashfs gcw0-assets ecwolf.opk -all-root -noappend -no-exports -no-xattrs -no-progress > /dev/null