Information wants to be free...

DVB-T USB Stick Playback

I got one of those DVB-T tuners a while ago, in the form of a USB stick, specifically this one:

15a4:1001 Afatech Technologies, Inc. AF9015/AF9035 DVB-T stick

However, it took me many years to finally get this working on Linux after a lot of trial and error. There are two important things I discovered during this time. The first is that MPlayer is quite bad at handling MPEG TS files (or streams). The second is that, at least in my case with this particular stick, the /dev/dvb/adapter0/dvr0 device would not work as advertised.

The solution I ended up with is w_scan for scanning, v4l-utils for tuning and ffmpeg for playback.

Use these steps to scan and generate the channels list:

w_scan --output-initial > channels.conf
dvbv5-scan --input-format=CHANNEL --output=dvb_channel.conf channels.conf

Then use these steps for playback:

mkfifo /tmp/dvb.ts
dvbv5-zap -c dvb_channel.conf "name-of-the-channel" -o /tmp/dvb.ts &
ffplay /tmp/dvb.ts

I found that TV (video and audio) playback works OK, but radio (audio only) will buffer for a very long time before playback starts, so not very convenient.

Topic: Configuration, by Kjetil @ 27/03-2021, Article Link

Toshiba Satellite Pro 410CDT Tweaks

I got hold of an old Toshiba Satellite Pro 410CDT laptop with a Pentium 90MHz processor, which I have cleaned up and refurbished. Since I already got tons of Linux boxes I figured to use this a "DOS Gaming Laptop" instead. It has a Sound Blaster compatible ESS688 sound chipset and a Adlib compatible FM synthesizer, making this perfect for that use.

Important notice! The internal batteries in this had already started to shown signs of leakage, the typical turquoise spots:

Toshiba 410CDT Batteries

I immediately removed the batteries and cleaned up the spots with vinegar. It will now complain about lost CMOS settings every time, but I can live with that for now.

Another challenge is that this laptop has no floppy drive, since that is swappable with a CD-ROM drive that I (only) got instead. To be able to install DOS I used QEMU to install it on a virtual drive, then removed the original hard drive from the laptop and DD'd over the virtual drive to it.

I knew the hard drive was 815394816 bytes, meaning 1592568 512-byte sectors, so a virtual drive can be made like this:

dd if=/dev/zero of=Toshiba_DOS.dd bs=512 count=1592568

QEMU is launched like this:

qemu-system-i386 -drive format=raw,file=Toshiba.dd -cpu pentium -m 32 -monitor stdio -fda DOS_Floppy_1.dd

One can then use the QEMU monitor to change and eject virtual floppies like so:

change floppy0 DOS_Floppy_2.dd
change floppy0 DOS_Floppy_3.dd
eject floppy0

Afterwards it is also possible to loopback mount he virtual hard drive to put more stuff on there, like tools and games. Since the first partition starts at sector 63, an offset of 32256 bytes must be used:

sudo mount -o loop,offset=32256 Toshiba_DOS.dd /mnt/loop/

I used one of those USB-to-IDE adapter and the virtual hard drive is typically DD'd back just like this:

dd if=Toshiba_DOS.dd of=/dev/sdd bs=512 status=progress

Finally, for reference, here is the "AUTOEXEC.BAT" file I ended up using for the laptop:

C:\ESSUTIL\ESSVOL.EXE /V:8 /L:8 /W:8 /M:0 /C:8 /S:8
SET BLASTER=A220 I7 D1 T6 P330 H5

And the "CONFIG.SYS" file:


Topic: Configuration, by Kjetil @ 15/11-2020, Article Link

Compaq Deskpro XL 5133 with Red Hat 5.2

I decided to install the classic Red Hat Linux 5.2 distribution on my classic Compaq Deskpro XL 5133 machine. The 5.2 version is one of the more well known from the late 90's, and several others have used this to experience the past. It is using the 2.0.36 version of the Linux kernel.

Before any of the SW installation could take place, the on-board battery had to be changed to be able to keep the system configuration intact. Luckily the battery is a Lithium type, so it doesn't leak, but it was soldered in place. I changed it with a CR2032 battery holder, which works fine.

CR2032 battery holder replacement

After configuring the system with the special Compaq floppy disks (there is no BIOS setup menu!) I was able to install Red Hat 5.2 using the CD-ROM without any trouble. The machine has a Matrox Millennium VGA card which works fine in X Windows and a on-board AMD PCnet32 Ethernet controller working out of the box.

The troublesome part was getting the audio to work, which is classified as "Compaq Deskpro XL Business Audio", but is in reality a "Microsoft Sound System" compatible chip of the AD1847 type:

AD1847JP SoundPort Chip

When playing any audio, it would stutter and the following error would appear:

Sound: DMA (output) timed out - IRQ/DRQ config error?

I tried all kinds of different IRQ and DMA settings, but to no avail. To troubleshoot further I setup a QEMU emulated environment also with Red Hat 5.2 to be able to quickly recompile the ad1848.o module device driver.

I figured out that in vanilla Linux 2.0.36 the sound drivers are not modularized, and Red Hat had actually applied a patch to modularize them. So this exact setup had to be re-recreated. The original sources can be found here as "kernel-2.0.36-0.7.src.rpm". But these still needs to be patched, where I did the following:

tar -xvzf linux-2.0.35.tar.gz
gunzip 2.0.36-pre-patch-14.gz
gunzip sound.diff.gz
patch -p0 < 2.0.36-pre-patch-14
patch -p0 < sound.diff
mv linux linux-2.0.36
patch -p0 < kernel-2.0.36-sound-new.patch
cp kernel-2.0.36-i386.config linux-2.0.36/.config

Yes, the original sources is actually Linux 2.0.35, but with a patch to bump it up to 2.0.36!

After enabling debugging flags, I eventually found out that this stock driver is detecting the audio chip wrongly as a "OPTi 82C930" chip, which in turn causes the IRQ status to be read from the wrong register!

Here is my own patch to fix this problem and enabling the debug:

--- ad1848.c.orig    2020-08-30 12:42:45.362175159 +0200
+++ ad1848.c    2020-08-30 12:42:52.142175232 +0200
@@ -37,6 +37,9 @@
 #include "soundmodule.h"
+#define DEBUGXL
+#define DDB
 #define DEB(x)
 #define DEB1(x)
 #include "sound_config.h"
@@ -1532,10 +1535,19 @@
         if ((tmp1 = ad_read(devc, i)) != (tmp2 = ad_read(devc, i + 16)))
-            DDB(printk("ad1848 detect step F(%d/%x/%x) - OPTi chip???\n", i, tmp1, tmp2));
-            if (!ad1847_flag)
-                optiC930 = 1;
-            break;
+            if (deskpro_xl)
+            {
+                DDB(printk("Deskpro XL, so assuming AD1847\n"));
+                ad1847_flag = 1;
+                break;
+            }
+            else
+            {
+                DDB(printk("ad1848 detect step F(%d/%x/%x) - OPTi chip???\n", i, tmp1, tmp2));
+                if (!ad1847_flag)
+                    optiC930 = 1;
+                break;
+            }
@@ -1688,7 +1700,10 @@
-                            devc->model = MD_4231;
+                            if (! deskpro_xl)
+                            {
+                                devc->model = MD_4231;
+                            }
@@ -1708,6 +1723,7 @@
     if (devc->model == MD_1848 && ad1847_flag)
         devc->chip_name = "AD1847";
+    DDB(printk("ad1848_detect() - '%s' (%d)\n", devc->chip_name, devc->model));
     return 1;

Or you can download my recompiled version here.

The /etc/conf.modules section ended up being like this for the driver:

alias sound ad1848
alias midi opl3
options opl3 io=0x388
options ad1848 io=0x530 irq=9 dma=1,0 type=2 deskpro_xl=1

Compaq Deskpro XL 5133

Topic: Configuration, by Kjetil @ 01/11-2020, Article Link

Linux Distribution for LOADLIN

This is a similar project to the Linux Distribution for 386SX but this with some different goals. Most importantly to boot it with LOADLIN directly from DOS and keeping the root filesystem in RAM using Cramfs. In addition, I wanted to have functioning SLIP support.

I ended up using these specific software versions:
* linux-
* gcc-3.4.6
* busybox-1.19.4
* uClibc-
* binutils-2.32

Get the necessary scripts, configuration and patches here to make it yourself. Or just get the completed kernel and root filesystem here.

For easy reference, here is the script to compile everything:

set -e



export PATH="${PREFIX}bin:$PATH"

# Prepare Prefix and System Root
if [ -d "$SYSROOT" ]; then
  echo "Old system root directory detected, please remove it."
  exit 1
  mkdir -p "$SYSROOT/usr"

# Prepare Build Directories:
if [ -d build ]; then
  echo "Old build directory detected, please remove it."
  exit 1
  mkdir -p build/binutils
  mkdir -p build/gcc-stage1
  mkdir -p build/gcc-stage2
  mkdir -p build/uclibc
  mkdir -p build/linux
  mkdir -p build/busybox

# Unpack Sources:
if [ -d source ]; then
  cd source
  tar -xvjf "$GCC_SRC"
  tar -xvJf "$BINUTILS_SRC"
  tar -xvJf "$UCLIBC_SRC" -C ../build/uclibc
  tar -xvJf "$LINUX_SRC" -C ../build/linux
  tar -xvjf "$BUSYBOX_SRC" -C ../build/busybox
  cd -
  echo "No source directory, please download sources."
  exit 1

# Patch gcc-3.4.6:
cd "source/gcc-3.4.6/gcc/config/i386/"
if ! fgrep --silent "inhibit_libc" linux.h; then
  patch -p 0 < ../../../../../gcc-3.4.6-linux.h.patch
cd -

# Patch linux-
cd "build/linux/linux-"
if ! fgrep --silent "<linux/types.h>" filter.h; then
  patch -p 0 < ../../../../../linux-
cd -

# Install Linux 2.4 Headers:
cd build/linux/linux-*
make ARCH=i386 mrproper
make ARCH=i386 include/linux/version.h
make ARCH=i386 symlinks
mkdir -p "$SYSROOT/usr/include/asm"
cp -v -R -H include/asm "$SYSROOT/usr/include"
cp -v -R include/asm-generic "$SYSROOT/usr/include"
cp -v -R include/linux "$SYSROOT/usr/include"
touch "${SYSROOT}/usr/include/linux/autoconf.h"
cd -

# Build binutils:
cd build/binutils
../../source/binutils-*/configure --target="$TARGET" --prefix="$PREFIX" --with-sysroot="$SYSROOT" --disable-werror --enable-languages=c,c++ --enable-shared --without-newlib --disable-libgomp --enable-fast-install=N/A
make all-{binutils,gas,ld}
make install-{binutils,ld,gas}
cd -

# Build Stage 1 GCC3:
cd build/gcc-stage1
../../source/gcc-3*/configure --target="$TARGET" --prefix="$PREFIX" --with-sysroot="$SYSROOT" --with-cpu=i386 --disable-fast-install --disable-werror --disable-multilib --enable-languages=c --without-headers --disable-shared --disable-libssp --disable-libmudflap --with-newlib --disable-c99 --disable-libgomp --disable-threads
make all-gcc
make install-gcc
cd -

# Install uClibc Headers:
cd build/uclibc/uClibc-*
cp -v ../../../config-uclibc .config
sed -i -e "s%KERNEL_HEADERS=.*%KERNEL_HEADERS=\"$SYSROOT/usr/include/\"%" .config
make ARCH=i386 PREFIX="$SYSROOT" install_headers
cd -

# Build uClibc:
cd build/uclibc/uClibc-*
make ARCH=i386 PREFIX="$SYSROOT" install
cd -

# Build Stage 2 GCC3:
cd build/gcc-stage2
../../source/gcc-3*/configure --target="$TARGET" --prefix="$PREFIX" --with-sysroot="$SYSROOT" --with-cpu=i386 --enable-fast-install=N/A --disable-werror --enable-languages=c,c++ --disable-shared --without-newlib --disable-libgomp --disable-threads
make all-gcc
make install-gcc
cd -

# Build Linux 2.4:
cd build/linux/linux-*
cp -v ../../../config-linux .config
make ARCH=i386 CROSS_COMPILE=i386-linux-uclibc- oldconfig
make ARCH=i386 CROSS_COMPILE=i386-linux-uclibc- dep
make ARCH=i386 CROSS_COMPILE=i386-linux-uclibc- bzImage
cd -

# Build Busybox:
cd build/busybox/busybox-*
cp -v ../../../config-busybox .config
make CROSS_COMPILE=i386-linux-uclibc-
cd -

And here is the script to make the root filesystem:

set -e



export PATH="${PREFIX}bin:$PATH"

if [ -d "$ROOTFS" ]; then
  echo "Old root FS directory detected, please remove it."
  exit 1
mkdir -p "$ROOTFS"

# Install Busybox:
cd build/busybox/busybox-*
make CROSS_COMPILE=i386-linux-uclibc- CONFIG_PREFIX="$ROOTFS" install
cd -

# Create some essential directories
cd "$ROOTFS"
mkdir etc
mkdir etc/init.d
mkdir lib
mkdir proc
mkdir sys
mkdir tmp
mkdir root
mkdir dev
mkdir dev/pts
cd -

# Initial rc.S:
cat > rcS <<EOF
mount -t proc /proc /proc
mount -t devpts /dev/pts /dev/pts
mount -t tmpfs /tmp /tmp
loadkmap < /etc/no-latin1.bmap
hostname busybox
mv -v rcS "$ROOTFS/etc/init.d/"

# Initial inittab:
cat > inittab <<EOF
::shutdown:/bin/umount -a -r
mv -v inittab "$ROOTFS/etc/"

# Copy this system's keymap:
loadkeys -b /usr/share/kbd/keymaps/i386/qwerty/ > "$ROOTFS/etc/no-latin1.bmap"

# Make everything root user:
sudo chown -R root:root "$ROOTFS"

# Create some critical devices:
sudo mknod "$ROOTFS/dev/tty" c 5 0
sudo mknod "$ROOTFS/dev/console" c 5 1
sudo mknod -m 0666 "$ROOTFS/dev/null" c 1 3

# Create some useful devices:
sudo mknod "$ROOTFS/dev/rtc" c 10 135
sudo mknod "$ROOTFS/dev/tty0" c 4 0
sudo mknod "$ROOTFS/dev/tty1" c 4 1
sudo mknod "$ROOTFS/dev/tty2" c 4 2
sudo mknod "$ROOTFS/dev/tty3" c 4 3
sudo mknod "$ROOTFS/dev/ttyS0" c 4 64
sudo mknod "$ROOTFS/dev/ttyS1" c 4 65
sudo mknod "$ROOTFS/dev/fd0" b 2 0
sudo mknod "$ROOTFS/dev/fd1" b 2 1
sudo mknod "$ROOTFS/dev/root" b 4 0
sudo mknod "$ROOTFS/dev/lp0" c 6 0

# SetUID on busybox binary:
sudo chmod +s "$ROOTFS/bin/busybox"

# Make rcS executable:
sudo chmod +x "$ROOTFS/etc/init.d/rcS"

# Make Compressed ROM archive:
mkfs.cramfs rootfs rootfs.cramfs

Instead of using LOADLIN, it is actually easy to start this with QEMU as well, like so:

qemu-system-i386 -kernel bzImage -initrd rootfs.cramfs

Topic: Configuration, by Kjetil @ 12/09-2020, Article Link

Outlaws in Wine

I have made an effort in getting the classic LucasArts game Outlaws working in Wine with music, which is essential due to its excellent soundtrack. This is a similar to my other effort with M.I.A., except this time the music playback mechanism is more advanced. I had to make additional hacks to the Wine "mcicda" library to make it pause and resume in the middle of tracks.

Here is a rough guide for the commands required for installation. Also, when the installation asks about DirectX 3.0A, just skip this.

mkdir -p ~/opt/outlaws
mkdir ~/opt/outlaws/cd1 # Then copy files from CD1 into here.
mkdir ~/opt/outlaws/cd2 # Then copy files from CD2 into here.
echo "OUTLAWS_1" > ~/opt/outlaws/cd1/.windows-label
echo "OUTLAWS_2" > ~/opt/outlaws/cd2/.windows-label
ln -s "cd1" ~/opt/outlaws/drive_d
WINEARCH=win32 WINEPREFIX=~/opt/outlaws winecfg # D: = "drive_d" = CD-ROM
WINEARCH=win32 WINEPREFIX=~/opt/outlaws wine ~/opt/outlaws/drive_d/SETUP.EXE

I had to enable the "Virtual Desktop" setting with "winecfg" for certain stuff like menus to work. I also just configured my X windows resolution to 800x600 before playing the game.

To make the music playback work, a lot of additional steps are required. First the patched "mcicda.dll", which can be downloaded here. This should placed at "~/opt/outlaws/drive_c/windows/system32/mcicda.dll"

Here is the patched code in case you want to compile it yourself, using the Wine 4.0.2 source code as a basis:

--- mcicda.c.orig	2020-08-22 14:22:13.861217377 +0200
+++ mcicda.c	2020-08-22 14:22:18.323217425 +0200
@@ -20,11 +20,21 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+#define MPLAYER_FIFO_LOCATION "/tmp/mplayer.fifo"
+#define TOC_FILE_LOCATION "/tmp/toc.txt"
 #include "config.h"
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <time.h>
 #define WIN32_NO_STATUS
 #include "windef.h"
 #include "winbase.h"
@@ -79,10 +89,211 @@
 static LPDIRECTSOUNDCREATE pDirectSoundCreate;
+static int mplayer_current_track = 0;
+static int mplayer_current_min   = 0;
+static int mplayer_current_sec   = 0;
+static int mplayer_current_frame = 0;
+static struct timespec mplayer_playback_started = {0,0};
+static void mplayer_command(const char *command)
+    int fd, written;
+    if (fd == -1) {
+        TRACE("No pipe\n");
+        return;
+    }
+    written = write(fd, command, strlen(command));
+    if (written <= 0) {
+        TRACE("Write failed\n");
+    }
+    close(fd);
 static BOOL device_io(HANDLE dev, DWORD code, void *inbuffer, DWORD insize, void *outbuffer, DWORD outsize, DWORD *retsize, OVERLAPPED *overlapped)
     const char *str;
-    BOOL ret = DeviceIoControl(dev, code, inbuffer, insize, outbuffer, outsize, retsize, overlapped);
+    int track_no, min, sec, frame;
+    BOOL ret = TRUE;
+    CDROM_TOC *toc;
+    SUB_Q_CHANNEL_DATA *qdata;
+    FILE *fh;
+    char buf[16];
+    struct timespec now;
+    *retsize = 0;
+    switch (code) {
+        toc = (CDROM_TOC *)outbuffer;
+        *retsize = CDROM_TOC_SIZE;
+        toc->Length[0] = 0;
+        toc->Length[1] = 0;
+        toc->FirstTrack = 1;
+        toc->LastTrack = 1;
+        // Set up first track as data track.
+        toc->TrackData[0].TrackNumber = 1;
+        toc->TrackData[0].Control = 0x4;
+        toc->TrackData[0].Address[1] = 0;
+        toc->TrackData[0].Address[2] = 0;
+        toc->TrackData[0].Address[3] = 0;
+        // Get other audio tracks from toc file.
+        fh = fopen(TOC_FILE_LOCATION, "r");
+        if (fh == NULL) {
+          TRACE("IOCTL_CDROM_READ_TOC, Failed to open: %s\n", TOC_FILE_LOCATION);
+          break;
+        }
+        while (fgets(buf, sizeof(buf), fh) != NULL)
+        {
+          sscanf(buf, "%02d:%02d:%02d", &min, &sec, &frame);
+          toc->TrackData[toc->LastTrack].TrackNumber = toc->LastTrack + 1;
+          toc->TrackData[toc->LastTrack].Control = 0;
+          toc->TrackData[toc->LastTrack].Address[1] = min;
+          toc->TrackData[toc->LastTrack].Address[2] = sec;
+          toc->TrackData[toc->LastTrack].Address[3] = frame;
+          toc->LastTrack++;
+          TRACE("IOCTL_CDROM_READ_TOC, Track %d = %02d:%02d:%02d\n", toc->LastTrack, min, sec, frame);
+        }
+        toc->LastTrack--; // Remove the last dummy track:
+        fclose(fh);
+        break;
+        mplayer_command("stop\n");
+        break;
+        mplayer_command("stop\n");
+        break;
+        qfmt  = (CDROM_SUB_Q_DATA_FORMAT *)inbuffer;
+        qdata = (SUB_Q_CHANNEL_DATA *)outbuffer;
+        *retsize = sizeof(SUB_Q_CHANNEL_DATA);
+        if (qfmt->Format == IOCTL_CDROM_CURRENT_POSITION)
+        {
+            qdata->CurrentPosition.FormatCode = IOCTL_CDROM_CURRENT_POSITION;
+            qdata->CurrentPosition.Control = 0;
+            qdata->CurrentPosition.ADR = 0;
+            qdata->CurrentPosition.TrackNumber = mplayer_current_track;
+            qdata->CurrentPosition.IndexNumber = 0;
+            clock_gettime(CLOCK_MONOTONIC, &now);
+            TRACE("IOCTL_CDROM_READ_Q_CHANNEL, Started = %lu.%lu\n",
+                mplayer_playback_started.tv_sec, mplayer_playback_started.tv_nsec);
+            TRACE("IOCTL_CDROM_READ_Q_CHANNEL, Now = %lu.%lu\n", now.tv_sec, now.tv_nsec);
+            min = (now.tv_sec - mplayer_playback_started.tv_sec) / 60;
+            sec = (now.tv_sec - mplayer_playback_started.tv_sec) % 60;
+            frame = (now.tv_nsec - mplayer_playback_started.tv_nsec);
+            if (frame < 0) {
+                frame = 0 - frame;
+                sec--;
+                if (sec < 0) {
+                  sec = 59;
+                  min--;
+                }
+            }
+            frame = (frame / 10000000) * 0.75;
+            if (mplayer_current_min > 0 ||
+                mplayer_current_sec > 0 ||
+                mplayer_current_frame > 0)
+            {
+                min += mplayer_current_min;
+                sec += mplayer_current_sec;
+                if (sec >= 60) {
+                    sec -= 60;
+                    min++;
+                }
+                frame += mplayer_current_frame;
+                if (frame >= 76) {
+                    frame -= 76;
+                    sec++;
+                    if (sec >= 60) {
+                        sec -= 60;
+                        min++;
+                    }
+                }
+            }
+            qdata->CurrentPosition.TrackRelativeAddress[0] = 0;
+            qdata->CurrentPosition.TrackRelativeAddress[1] = min;
+            qdata->CurrentPosition.TrackRelativeAddress[2] = sec;
+            qdata->CurrentPosition.TrackRelativeAddress[3] = frame;
+            qdata->CurrentPosition.AbsoluteAddress[0] = 0;
+            qdata->CurrentPosition.AbsoluteAddress[1] = min;
+            qdata->CurrentPosition.AbsoluteAddress[2] = sec;
+            qdata->CurrentPosition.AbsoluteAddress[3] = frame;
+            fh = fopen(TOC_FILE_LOCATION, "r");
+            if (fh == NULL) {
+              TRACE("IOCTL_CDROM_READ_Q_CHANNEL, Failed to open: %s\n", TOC_FILE_LOCATION);
+              break;
+            }
+            track_no = 2;
+            while (fgets(buf, sizeof(buf), fh) != NULL)
+            {
+                if (track_no == mplayer_current_track) {
+                    sscanf(buf, "%02d:%02d:%02d", &min, &sec, &frame);
+                    qdata->CurrentPosition.AbsoluteAddress[1] += min;
+                    qdata->CurrentPosition.AbsoluteAddress[2] += sec;
+                    if (qdata->CurrentPosition.AbsoluteAddress[2] >= 60) {
+                        qdata->CurrentPosition.AbsoluteAddress[2] -= 60;
+                        qdata->CurrentPosition.AbsoluteAddress[1]++;
+                    }
+                    qdata->CurrentPosition.AbsoluteAddress[3] += frame;
+                    if (qdata->CurrentPosition.AbsoluteAddress[3] >= 76) {
+                        qdata->CurrentPosition.AbsoluteAddress[3] -= 76;
+                        qdata->CurrentPosition.AbsoluteAddress[2]++;
+                        if (qdata->CurrentPosition.AbsoluteAddress[2] >= 60) {
+                            qdata->CurrentPosition.AbsoluteAddress[2] -= 60;
+                            qdata->CurrentPosition.AbsoluteAddress[1]++;
+                        }
+                    }
+                    break;
+                }
+                track_no++;
+            }
+            fclose(fh);
+            TRACE("IOCTL_CDROM_READ_Q_CHANNEL, Current Track = %d\n", mplayer_current_track);
+            TRACE("IOCTL_CDROM_READ_Q_CHANNEL, Rel Pos = %02d:%02d:%02d\n",
+                qdata->CurrentPosition.TrackRelativeAddress[1],
+                qdata->CurrentPosition.TrackRelativeAddress[2],
+                qdata->CurrentPosition.TrackRelativeAddress[3]);
+            TRACE("IOCTL_CDROM_READ_Q_CHANNEL, Abs Pos = %02d:%02d:%02d\n",
+                qdata->CurrentPosition.AbsoluteAddress[1],
+                qdata->CurrentPosition.AbsoluteAddress[2],
+                qdata->CurrentPosition.AbsoluteAddress[3]);
+        }
+        else
+        {
+           TRACE("IOCTL_CDROM_READ_Q_CHANNEL, Unknown format: %d\n", qfmt->Format);
+        }
+        break;
+    default:
+        break;
+    }
 #define XX(x) case (x): str = #x; break
     switch (code)
@@ -906,6 +1117,9 @@
     SUB_Q_CHANNEL_DATA          data;
     CDROM_TOC			toc;
+    int track_no, min, sec, frame;
+    char command[PATH_MAX];
     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
     if (lpParms == NULL)
@@ -914,6 +1128,35 @@
     if (wmcda == NULL)
+    mplayer_command("stop\n");
+    track_no = MCI_TMSF_TRACK(lpParms->dwFrom);
+    min      = MCI_TMSF_MINUTE(lpParms->dwFrom);
+    sec      = MCI_TMSF_SECOND(lpParms->dwFrom);
+    frame    = MCI_TMSF_FRAME(lpParms->dwFrom);
+    TRACE("Track no: %d (%02d:%02d:%02d)\n", track_no, min, sec, frame);
+    snprintf(command, PATH_MAX, "loadfile track%02d.flac\n", track_no);
+    mplayer_command(command);
+    mplayer_current_track = track_no;
+    clock_gettime(CLOCK_MONOTONIC, &mplayer_playback_started);
+    if (min > 0 || sec > 0 || frame > 0) {
+      mplayer_current_min = min;
+      mplayer_current_sec = sec;
+      mplayer_current_frame = frame;
+      sec += (min * 60);
+      frame *= 1.333333;
+      TRACE("Seek to: %d.%02d\n", sec, frame);
+      snprintf(command, PATH_MAX, "seek %d.%02d 2\n", sec, frame);
+      mplayer_command(command);
+    }
+    return 0; // Because of hijacking, this ends here.
     if (!MCICDA_ReadTOC(wmcda, &toc, &br))
         return MCICDA_GetError(wmcda);

To make music playback work as painlessly as possible, I have once again made a script to start the game:

export WINEPREFIX=~/opt/outlaws


function mplayer_stop {
  if [ -f "$MPLAYER_PID_FILE" ]; then
    kill `cat "$MPLAYER_PID_FILE"`
    rm -f "$MPLAYER_PID_FILE"
  rm -f "$MPLAYER_FIFO"

function mplayer_start {
  if [ ! -p "$MPLAYER_FIFO" ]; then
    mkfifo "$MPLAYER_FIFO"
  cd "${WINEPREFIX}/drive_d"
  mplayer -vo null -idle -slave -input file=$MPLAYER_FIFO 1&>/dev/null &
  echo "$!" > "$MPLAYER_PID_FILE"
  cd -

while [ "$CD_NO" != "1" ] && [ "$CD_NO" != "2" ]; do
  read -p "CD number? [1 or 2] " CD_NO

while [ "$MODE" != "g" ] && [ "$MODE" != "c" ]; do
  read -p "Start [g]ame or [c]hange CD only? " MODE


rm -f "${WINEPREFIX}/drive_d"
ln -s "cd$CD_NO" "${WINEPREFIX}/drive_d"
cp "${WINEPREFIX}/drive_d/toc.txt" "$TOC_FILE"


if [ "$MODE" == "c" ]; then
  # If only changing CD, exit now.
  exit 0

(cd "${WINEPREFIX}/drive_c/Program Files/LucasArts/Outlaws" && WINEARCH=win32 wine OLWIN.EXE)

rm -f "$TOC_FILE"

The same script is also used to change the CD while the game is running, as this is required in some instances.

The CD audio tracks should be ripped to FLAC format and be placed as track02.flac to track08.flac in "~/opt/outlaws/cd1/" for CD1, followed by track02.flac to track09.flac in "~/opt/outlaws/cd2/" for CD2. In addition, you will need a couple of "Table of Contents" files for each CD. These will inform the patched "mcicda.dll" file about the length of each track.

Put the following in "~/opt/outlaws/cd1/toc.txt"


Put the following in "~/opt/outlaws/cd2/toc.txt"



Topic: Configuration, by Kjetil @ 24/08-2020, Article Link

M.I.A. in Wine

M.I.A.: Missing In Action is a relatively unknown game that came out for Windows back in 1998. I got the game back then in my childhood as part of some bundle with a new computer. I do remember the game being fun, but I had issues getting it to run later on because it will only install on Windows 98. Fast forward some 20 years, Wine has now become a better Windows than Windows on Linux in many aspects, especially running older games.
I have been using Wine version 4.0.2 for these experiments.

Using some tricks I finally got to run and play this on Linux! In order to get there I had do spend some time with both Winedbg and OllyDbg debuggers to figure out what the game tried to do and failed on.

The first problem was the detection of the CDs, which I figured out it does by calling GetVolumeInformationA() and looking at the volume label. This is fixed by creating a ".windows-label" file in the emulated CD drive with the correct label.

The second problem was getting the CD audio to work correctly. Apart from having to fake this somehow, the game uses the ancient Media Control Interface which still have some missing features (bugs?) in Wine at the time of writing. Maybe this will be fixed in an upcoming version, but I had no time to wait for that. The root of the problem is that Wine returns the code (as string) "1088" instead of the string "audio" when the game asks what type of track is on the CD. The quickest way to fix this is to simply patch the game binary to look for that other string.

To actually play the CD audio without the CDs I figured out it was easiest to hack the Wine "mcicda.dll" library and make it call MPlayer to play the tracks as .FLAC files. This is done in MPlayer's FIFO mode to avoid blocking anything.

A third problem is that the in-game video cutscenes, using Smacker Video Technology still does not play correctly in Wine. The symptom is that the video may play for some seconds, but then just hangs. Since it's possible to bypass this by hitting Escape, I have simply ignored this for now.

Anyway, the common steps to install and run are as follows:
1) Create a new directory to store a Wine prefix for M.I.A.:
mkdir -p ~/opt/mia
2) Run winecfg on the prefix, in 32-bit mode, and set it has "Windows 98":
WINEARCH=win32 WINEPREFIX=~/opt/mia winecfg
3) Create two directories for each M.I.A. CD in the prefix:
mkdir ~/opt/mia/cd1
mkdir ~/opt/mia/cd2
4) Copy the CD contents into the respective directories.
Either directly from the CDs or ISO images mounted as loopback devices.
5) Create fake volume labels for each CD, as needed by the game:
echo "MIA_VOL1" > ~/opt/mia/cd1/.windows-label
echo "MIA_VOL2" > ~/opt/mia/cd2/.windows-label
6) Create a symbolic link kalled "drive_d" pointing to "cd1"
ln -s "cd1" ~/opt/mia/drive_d
7) Run winecfg again to map D: to the newly created "drive_d" directory.
Also set the type as "CD-ROM".
WINEARCH=win32 WINEPREFIX=~/opt/mia winecfg
8) Allow low memory to be mapped, since it is needed by M.I.A. installer.
sudo sysctl vm.mmap_min_addr=0
9) Start the M.I.A. installation through Wine,
WINEARCH=win32 WINEPREFIX=~/opt/mia wine ~/opt/mia/drive_d/mia.exe
10) When prompted for installation of installation type...
Select "Leave ground textures on CD".
This is needed because the installer has issues finding CD #2.
11) Run the following script to easily start the game:

export WINEPREFIX=~/opt/mia

sudo sysctl vm.mmap_min_addr=0 # Wine needs to be allowed to map low memory.

while [ "$CD_NO" != "1" ] && [ "$CD_NO" != "2" ]; do
  read -p "CD Number? (1 or 2) " CD_NO

rm -f "${WINEPREFIX}/drive_d"
ln -s "cd$CD_NO" "${WINEPREFIX}/drive_d"

(cd "${WINEPREFIX}/drive_c/MIA" && WINEARCH=win32 wine miarel.exe -avhpd)

If you also want the in-game music, some additional steps are required:
12) Assuming all tracks are ripped from the CD's in FLAC format.
4 audio tracks from CD #1 named from "track02.flac" to "track05.flac".
5 audio tracks from CD #2 mamed from "track02.flac" to "track06.flac".
Copy .flac files into the root of each respective directory "cd1" and "cd2".
13) Patch the "mcicda.dll" file in ~/opt/mia/drive_c/windows/system32/
14) Patch the "miarel.exe" file in ~/opt/mia/drive_c/MIA/
15) Run the following modified script to start the game:

export WINEPREFIX=~/opt/mia

MPLAYER_FIFO=/tmp/mplayer.fifo # Do not change, hardcoded in patched mcicda.dll

sudo sysctl vm.mmap_min_addr=0 # Wine needs to be allowed to map low memory.

while [ "$CD_NO" != "1" ] && [ "$CD_NO" != "2" ]; do
  read -p "CD Number? (1 or 2) " CD_NO

rm -f "${WINEPREFIX}/drive_d"
ln -s "cd$CD_NO" "${WINEPREFIX}/drive_d"

if [ ! -p "$MPLAYER_FIFO" ]; then
  mkfifo "$MPLAYER_FIFO"

cd "${WINEPREFIX}/drive_d"
mplayer -idle -slave -input file=$MPLAYER_FIFO 1&>/dev/null &
cd -

(cd "${WINEPREFIX}/drive_c/MIA" && WINEARCH=win32 wine miarel.exe -avhpd)


To patch "miarel.exe", open it in a hex-editor and go to offset 0x1427c4. At this location the string "audio" should be present. Replace this with 0x31 0x30 0x38 0x38 0x00 which represents the string "1088" with an additional NULL terminator.

Patching "mcicda.dll" is more complicated as it requires a rebuild of Wine. Get the Wine source code and apply the following source code patch to "./dlls/mcicda/mcicda.c":

--- mcicda.c.orig	2020-05-13 17:55:02.433346437 +0200
+++ mcicda.c	2020-05-13 17:54:53.230346338 +0200
@@ -25,6 +25,12 @@
 #include <stdio.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
 #define WIN32_NO_STATUS
 #include "windef.h"
 #include "winbase.h"
@@ -79,10 +85,72 @@
 static LPDIRECTSOUNDCREATE pDirectSoundCreate;
+static void mplayer_command(const char *command)
+    int fd, written;
+    fd = open("/tmp/mplayer.fifo", O_NONBLOCK | O_WRONLY);
+    if (fd == -1) {
+        TRACE("No pipe\n");
+        return;
+    }
+    written = write(fd, command, strlen(command));
+    if (written <= 0) {
+        TRACE("Write failed\n");
+    }
+    close(fd);
 static BOOL device_io(HANDLE dev, DWORD code, void *inbuffer, DWORD insize, void *outbuffer, DWORD outsize, DWORD *retsize, OVERLAPPED *overlapped)
     const char *str;
-    BOOL ret = DeviceIoControl(dev, code, inbuffer, insize, outbuffer, outsize, retsize, overlapped);
+//    BOOL ret = DeviceIoControl(dev, code, inbuffer, insize, outbuffer, outsize, retsize, overlapped);
+    int i;
+    BOOL ret = TRUE;
+    CDROM_TOC *toc;
+    *retsize = 0;
+    switch (code) {
+        toc = (CDROM_TOC *)outbuffer;
+        toc->FirstTrack = 1;
+        toc->LastTrack = 6;
+        // Set up first track as data track.
+        toc->TrackData[0].TrackNumber = 1;
+        toc->TrackData[0].Control = 0x4;
+        toc->TrackData[0].Address[1] = 0;
+        toc->TrackData[0].Address[2] = 0;
+        toc->TrackData[0].Address[3] = 0;
+        // Set up remaining tracks as dummy audio tracks.
+        for (i = 1; i < toc->LastTrack; i++) {
+            toc->TrackData[i].TrackNumber = i + 1;
+            toc->TrackData[i].Control = 0;
+            toc->TrackData[i].Address[1] = i;
+            toc->TrackData[i].Address[2] = 0;
+            toc->TrackData[i].Address[3] = 0;
+        }
+        *retsize = CDROM_TOC_SIZE;
+        break;
+        mplayer_command("stop\n");
+        break;
+        mplayer_command("stop\n");
+        break;
+    default:
+        break;
+    }
 #define XX(x) case (x): str = #x; break
     switch (code)
@@ -906,6 +974,9 @@
     SUB_Q_CHANNEL_DATA          data;
     CDROM_TOC			toc;
+    int track_no;
+    char command[PATH_MAX];
     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
     if (lpParms == NULL)
@@ -914,6 +985,20 @@
     if (wmcda == NULL)
+    mplayer_command("stop\n");
+    track_no = MCI_TMSF_TRACK(lpParms->dwFrom);
+    TRACE("Track no: %d\n", track_no);
+    snprintf(command, PATH_MAX, "loadfile track%02d.flac\n", track_no);
+    mplayer_command(command);
+    return 0;
+    // HIJACK END
     if (!MCICDA_ReadTOC(wmcda, &toc, &br))
         return MCICDA_GetError(wmcda);

To build the DLL file, it should be enough to run ./configure and make on the Wine source code. The resulting file will be named "" but can be renamed to "mcicda.dll".

Topic: Configuration, by Kjetil @ 24/05-2020, Article Link

Dell 2005FPW Display Resolution Fix

I have an old Dell 2005FPW LCD display from around 2005. I had some problems getting this to display the native 1680x1050 resolution in Linux. Instead it would always want to display 1280x1024, and everything would look ugly. The cause of this seems to be bad EDID data being sent by the monitor, because I managed to fix it by manually creating a EDID firmware file with a resolution of 1680x1050 and the refresh rate forced to 59Hz, instead of the more common 60Hz.

There is a nice piece of software here on GitHub to create EDID firmware files from X modeline configuration. I used this together with this handy Online modeline generator.

Here is the modeline generated from the online tool:

# 1680x1050 @ 59.00 Hz (GTF) hsync: 64.07 kHz; pclk: 144.55 MHz
Modeline "1680x1050_59.00" 144.55 1680 1784 1968 2256 1050 1051 1054 1086 -HSync +Vsync

I had to trim away the excess ".00" from the string, or else the EDID firwmare file would be too big, 131 bytes instead of 128 bytes, so this should be the actual input to the 'modeline2edid' tool in the EDID generator package:

Modeline "1680x1050_59" 144.55 1680 1784 1968 2256 1050 1051 1054 1086 -HSync +Vsync

For reference, here is a hexdump of the newly created EDID firmware:

00000000  00 ff ff ff ff ff ff 00  31 d8 00 00 00 00 00 00  |..1......|
00000010  05 16 01 03 6d 2b 1b 78  ea 5e c0 a4 59 4a 98 25  |....m+.x^YJ.%|
00000020  20 50 54 00 00 00 b3 00  01 01 01 01 01 01 01 01  | PT............|
00000030  01 01 01 01 01 01 77 38  90 40 62 1a 24 40 68 b8  |......w8.@b.$@h|
00000040  13 00 b5 11 11 00 00 1e  00 00 00 ff 00 4c 69 6e  |...........Lin|
00000050  75 78 20 23 30 0a 20 20  20 20 00 00 00 fd 00 3b  |ux #0.    ....;|
00000060  3d 3f 41 0f 00 0a 20 20  20 20 20 20 00 00 00 fc  |=?A...      ...|
00000070  00 31 36 38 30 78 31 30  35 30 5f 35 39 0a 00 e1  |.1680x1050_59..|

The file needs to be loaded by putting it in /lib/firmware/edid/ and adding this to the Linux boot kernel parameters:


After this, the resolution became correct in both the framebuffer console and X on the computer where I use this LCD display. The computer has a NVIDIA GeForce FX 5200 graphics gard with the "nouveau" driver in use.

Topic: Configuration, by Kjetil @ 02/04-2020, Article Link

Linux Distribution for 386SX

I have a Commodore PC 50-II system with a Intel 386SX CPU running at 16MHz and only 5MB of RAM, in addition, the harddrive controller and harddrive is also missing. To get around this, I have installed a 3Com 3C509B-TPO ISA Network card and have managed to boot Linux on this ancient PC over the network instead.

At first I tried to use Buildroot, but I had to give up on this in the end, as it seems that pure 386 is not supported anywhere anymore. So instead, I have gone the route of building everything from scratch, so I could pick and choose specific source versions of GCC and the Linux kernel that supports 386 with math emulation. The end result is a cross-compiling toolchain for i386, a stripped down kernel and Busybox-based rootfs mounted over NFS from another host computer.

I ended up using these specific software versions:
* linux-
* gcc-3.4.6
* busybox-1.01
* uClibc-
* binutils-2.32

I also had to statically link everything. I tried with dynamic linking in the beginning but the system would hang during boot, and I didn't want to troubleshoot this any further.

I have made some scripts to greatly simplify the whole process of building the cross-compiling toolchain and all the binaries. Bundled in a file here.

Unpack this into a directory and then simply run the following:


I have used these on a Slackware 14.2 box with GCC 5.3.0
The result will be a directory with the "rootfs" and a "bzImage" located inside the kernel build tree.

The rootfs is exported over NFS in /etc/exports like this:

/home/nfs/rootfs *(rw,sync,no_root_squash,no_all_squash,no_subtree_check)

Also note that the legacy NFSv2 protocol needs to be actived!

To prepare the kernel is the tricky part. To load it over TFTP with Etherboot you have to use the mkelf-linux script from the mknbi package. This package is not compilable on modern Linux distributions, so I ended up compiling and using it from an old Slackware 11.0 installation running in a QEMU virtual machine.

I used the command as follows:

mkelf-linux --output=pc50linux.nb --ip="" --append="root=/dev/nfs nfsroot=/home/nfs/rootfs" bzImage

Where .3 is the booted client PC, .2 is the NFS server and .1 is the gateway and TFTP server.

The final step is to activate the TFTP/DHCP server. I used dnsmasq for this, with the following relevant configuration:


Some additional information: I have no Boot ROM on the Ethernet card, so I used a floppy disk to actually kick-start the network booting. More specifically, the "3c509.dsk" image made from compiling parts of the "Etherboot" package. Since this also isn't compilable on modern distributions, I had to use the Slackware 11.0 QEMU virtual machine again.

Commodore PC 50-II

Topic: Configuration, by Kjetil @ 01/12-2019, Article Link

OpenVPN Setup for Android

There are probably many ways to do this, but this is what worked for me in the end, after several trials and errors. I ended up making a "standalone" server solution based on running in GNU Screen to avoid messing too much with my existing server.

I started by downloading the EasyRSA scripts to help generating certificates and such. Then ran the following commands:

./easyrsa init-pki
./easyrsa build-ca
./easyrsa build-server-full server
./easyrsa build-client-full client
./easyrsa gen-dh

You will have to enter a CA key passphrase and PEM passphrase, keep those for later.

Once the files are created, copy them into a new location where everything will be stored, in my case the "openvpn" directory under my home directory:

mkdir ~/openvpn
cp pki/ca.crt ~/openvpn/
cp pki/dh.pem ~/openvpn/
cp pki/issued/client.crt ~/openvpn/
cp pki/issued/server.crt ~/openvpn/
cp pki/private/ca.key ~/openvpn/
cp pki/private/client.key ~/openvpn/
cp pki/private/server.key ~/openvpn/

The OpenVPN server configuration file must be created manually, at ~/openvpn/server.cfg with the following contents:

ca ca.crt
cert server.crt
key server.key
dh dh.pem
dev tun
port 1194
proto udp
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS"
push "ifconfig"
mode server
verb 4
client-config-dir ccd

Create a new directory "ccd" under the directory structure and create the file ~/openvpn/ccd/client with the following single line:


To be able to start things easily and open the necessary parts of the firewall a script like this can be used, placed at ~/openvpn/

screen -S openvpn -d -m sudo openvpn server.cfg

sudo iptables -A INPUT -p udp --dport 1194 -i eth0 -j ACCEPT
sudo iptables -A INPUT -i tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -d -j ACCEPT

This particular server already has iptables setup for NAT and such, so that is not present in this configuration.

Finally, the Android OpenVPN application requires a matching "ovpn" file with the client configuration. I had to make this one by manually looking something like this:

dev tun                             
proto udp
remote 1194
resolv-retry infinite
verb 3
<contents of client.crt file>
<contents of client.key file>
<contents of ca.crt file>

Topic: Configuration, by Kjetil @ 18/08-2019, Article Link

Commodore 64 Game Cheats

I recently tried out the same tricks from my older article about DOS games, but for a Commodore 64 game instead. This time using the VICE emulator for Blagger by Alligata, a game I could never finish as a kid.

I present some of the steps I used to create the "cheat" patch for infinite lives.
1) Start the game (should start with 5 lives) and go into the VICE monitor.
2) Dump the memory and state of the C64 with the "dump" command.
3) Continue the game, and just loose 1 life.
4) Re-enter the monitor and dump the memory and state again with the "dump" command.
5) Convert the dumps to hex and do a diff on them, to see what has changed. It may take a while to find what you are looking for, which may require additional dumps.
Eventually I found this suspicious memory area, where two values were decremented twice:

< 00000740  20 20 20 20 1e 57 57 1e  57 57 1e 7a 3a 3b 42 43  |    .WW.WW.z:;BC|
> 00000740  20 20 20 20 1e 57 57 1e  57 57 1e 7a 3a 3b 44 45  |    .WW.WW.z:;DE|

Although this is at address 0x74e and 0x74f in the dump file, the actual memory addresses are 0x6ca and 0x6cb due to the format of the dump.
6) Now, with an address to look at, set a watch point on this in the VICE monitor.
7) Continue the game again and loose another life.
8) The monitor should now stop execution automatically if done correctly. In my case it displayed:

(C:$1102) w $06ca
WATCH: 1  C:$06ca  (Stop on load store)
(C:$1102) x
#1 (Stop on  load 06ca)  076 050
.C:0de6  AE CA 06    LDX $06CA      - A:01 X:44 Y:00 SP:f6 ..-....C  184810550
.C:0de9  CA          DEX            - A:01 X:44 Y:00 SP:f6 ..-....C  184810550

This assembly instruction (DEX) means the memory area is being decremented, exactly what we are looking for.
9) After investigation of the assembly code in that area I found two decrement instructions. These could be patched with NOP (No Operation) instructions (6502 machine code 0xEA) from the VICE monitor like so:

(C:$0e0c) > $0de9 ea
(C:$0e0c) > $0dea ea

10) Now, when continuing to play the game, any lost life is simply ignored.

To apply a patch like this permanently, do it on the file (in this case a PRG file) instead of directly in memory of course. The code location will be different, so it must be searched for manually.

Topic: Configuration, by Kjetil @ 02/07-2019, Article Link

VPN Through SSH Tunnel

Not just VPN through an SSH tunnel, but also a Wi-Fi hotspot that directs all traffic through it! I needed this to be able to reach the Google Play store on my Android phone in China. I used two WLAN interfaces for this, but it should be possible with one WLAN and one wired connection as well.

VPN through SSH tunnel principle diagram.

Here are the complete steps, with the contents of the files listed after. Many of the commands are started through screen sessions, since they are daemons that will keep running.

First of all, make sure the interface that will be used to connect to the remote SSH server is up and running, then connect to the server and create the SSH tunnel for port 1194, which is used for VPN:

ssh -L

One the remote server, enter the following commands:

# Start OpenVPN server:
screen -S openvpn -d -m sudo openvpn server.cfg

# Forward all the traffic from the OpenVPN tunnel interface:
sudo iptables -A INPUT -i tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -d -j ACCEPT

# Enable IP forwarding on server:
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"

Back on the local machine, enter the following commands:

# IP address for Wi-Fi hotspot:
sudo ifconfig wlan0 netmask

# Start DHCP server for Wi-Fi hotspot:
screen -S dnsmasq -d -m sudo dnsmasq --conf-file=dnsmasq.conf --no-daemon

# Start Host-AP deamon:
screen -S hostapd -d -m sudo hostapd hostapd.conf

# Start OpenVPN client:
screen -S openvpn -d -m sudo openvpn client.cfg

# Let the SSH connection bypass the VPN default route:
SSH_IP=`host | sed -e 's/.*address //'`
GW_IP=`route -n | grep "^" | grep $SSH_IF | sed -e 's/ *//' | sed -e 's/ .*//'`
sudo route add $SSH_IP gw $GW_IP $SSH_IF

# Override the DNS resolver:
sudo cp resolv.conf /etc/resolv.conf

# Enable IP forwarding on local machine:
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"

# Enable NATing through the tunnel interface from the Wi-Fi hotspot:
sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
sudo iptables -A FORWARD -i tun0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i wlan0 -o tun0 -j ACCEPT

That should be it, now the phone can connect to the local Wi-Fi hotspot and reach the Internet through the remote SSH server.

Some useful commands for troubleshooting:

sudo iptables -L -v -n
sudo tcpdump -i tun0

OpenVPN client configuration. (client.cfg):

proto tcp-client
dev tun
secret static.key
redirect-gateway local def1

OpenVPN server configuration. (server.cfg):

dev tun
proto tcp-server
secret static.key

New DNS resolver configuration, using the Google DNS. This is important because it could have been set by a local DHCP client to a Chinese DNS resolver. (resolv.conf):


DHCP server configuration. (dnsmasq.conf):


Host-AP Wi-Fi hotspot configuration. These are the more important options, I have excluded lots of others. (hostapd.conf):

wpa_key_mgmt=WPA-PSK WPA-EAP

Topic: Configuration, by Kjetil @ 10/06-2018, Article Link

Renesas GCC Toolchains

The webpage has good information on how to build the Renesas RX and RL78 toolchains, but the GCC versions used there are a little outdated. And in addition, some patches are applied.

So instead I tried to build the toolchains from scratch directly from the GNU sources, and also get the benefit of the newer GCC version 7 instead of GCC version 4.

My efforts were successful and I have automated it into a single script. Simply change the TARGET variable to build either RL78 or the RX toolchain. It installs everything into a separate directoy in /opt/ on the filesystem. Take a look:

set -e


export PATH="${PREFIX}bin:$PATH"

# 1) Prepare build directories:
if [ -d build ]; then
  echo "Old build directory detected, please remove it."
  exit 1
  mkdir -p build/autoconf
  mkdir -p build/binutils
  mkdir -p build/gcc
  mkdir -p build/gdb
  mkdir -p build/newlib

# 2) Get sources:
if [ ! -d source ]; then
  mkdir source
  cd source
  wget ""
  wget ""
  wget ""
  wget ""
  wget ""
  tar -xvjf autoconf-2.64.tar.bz2
  tar -xvJf gcc-7.3.0.tar.xz
  tar -xvJf gdb-8.1.tar.xz
  tar -xvJf binutils-2.30.tar.xz
  tar -xvzf newlib-2.5.0.tar.gz
  cd ..

# 3) Build autoconf:
cd build/autoconf
../../source/autoconf-2.64/configure --prefix=$PREFIX
sudo make install
cd ..

# 4) Build binutils:
cd binutils
../../source/binutils-2.30/configure --target=$TARGET --prefix=$PREFIX --enable-maintainer-mode --disable-nls --disable-werror
sudo make install
cd ..

# 5) Get gcc sources:
if [ ! -d ../source/gcc-7.3.0/gmp ]; then
  cd ../source/gcc-7.3.0
  cd ../../build

# 6) Build gcc (step 1):
cd gcc
../../source/gcc-7.3.0/configure --target=$TARGET --prefix=$PREFIX --enable-languages=c,c++ --disable-shared --with-newlib --enable-lto --disable-libstdcxx-pch --disable-nls --disable-werror
make all-gcc
sudo make install-gcc
cd ..

# 7) Build newlib:
cd newlib
../../source/newlib-2.5.0/configure --target=$TARGET --prefix=$PREFIX --disable-nls
sudo make install
cd ..

# 8) Build gdb:
cd gdb
../../source/gdb-8.1/configure --target=$TARGET --prefix=$PREFIX --disable-nls
sudo make install
cd ..

# 9) Build gcc (step 2):
cd gcc
sudo make install

Topic: Configuration, by Kjetil @ 03/03-2018, Article Link

Fluxbox Styles

I almost exclusively use the Fluxbox window manager on my systems. Recently I have looked into how to create custom styles, and have finally been able to boil it down into something that's easy to manage.

Here's a desktop screenshot of a style I made:

Custom Fluxbox Style

But the most interesting part is the trimmed down style file:

*.textColor: #009900
*.color: black
*.focus.color: #009900
*.focus.textColor: black
*.focused.color: #009900
*.focused.textColor: black
*.font: glisp
*.borderWidth: 1
*.borderColor: #009900

menu.bullet: triangle
menu.bullet.position: right
menu.title.color: #009900
menu.title.textColor: black
menu.title.justify: center
menu.frame.color: black
menu.frame.textColor: green
menu.hilite.color: #009900
menu.hilite.textColor: black
menu.frame.disableColor: grey50

window.button.focus.picColor: black
window.button.unfocus.picColor: green

window.*.focus: raised gradient vertical
window.*.focus.colorTo: #00ee00
toolbar.*.focused: raised gradient vertical
toolbar.*.focused.colorTo: #00ee00
menu.title: raised gradient vertical
menu.title.colorTo: #00ee00

In the corner of the screenshot you'll also notice conky running. Here is the conkyrc file creating a similar colorscheme to match the Fluxbox style.

alignment top_right
background yes
border_width 1
cpu_avg_samples 2
default_color green
default_outline_color green
default_shade_color green
draw_borders no
draw_graph_borders yes
draw_outline no
draw_shades no
use_xft no
xftfont DejaVu Sans Mono:size=12
gap_x 5
gap_y 5
minimum_size 300 5
net_avg_samples 2
no_buffers yes
out_to_console no
out_to_stderr no
extra_newline no
own_window no
own_window_class Conky
own_window_type desktop
own_window_transparent yes
stippled_borders 0
update_interval 1.0
uppercase no
use_spacer none
show_graph_scale no
show_graph_range no
double_buffer yes

${color green4}Uptime:$color $uptime
${color green4}Battery:$color $battery ${battery_bar}
${color green4}RAM Usage:$color $mem/$memmax - $memperc% ${membar 4}
${color green4}CPU \#1:$color ${cpu cpu1}% ${cpubar cpu1 4}
${color green4}CPU \#2:$color ${cpu cpu2}% ${cpubar cpu2 4}
${color green4}CPU \#3:$color ${cpu cpu3}% ${cpubar cpu3 4}
${color green4}CPU \#4:$color ${cpu cpu4}% ${cpubar cpu4 4}
${color green4}Processes:$color $processes  ${color green4}Running:$color $running_processes
${color green4}Wired:$color ${addr eth0}
${color green4}Wireless:$color ${addr wlan0}

A final recommendation when using styles like this is to make sure that the "ls" command does not produce garish results. This setting helps:

export LS_COLORS='rs=00:di=01:ln=00:mh=00:pi=01:so=01:do=01:bd=01:cd=01:or=31;01:mi=00:su=00:sg=00:ca=00:tw=00:ow=00:st=00:ex=00:'

Topic: Configuration, by Kjetil @ 21/10-2017, Article Link

GR-KURUMI Makefiles for Linux

Here's some follow-up information on the GR-KURUMI microcontroller reference board mentioned in the previous article.

On the official Gadget Renesas pages you can find an online editor and compiler refered to as the "Web Compiler", but in case you want to build the sources on your own Linux box then some more work is required.

First of all, download and build the RL78 GCC toolchain. Follow this advice.

Second, you will need the linker script, crt0 and header files which are specific for the RL78. I got this from the Gadget Renesas Web Compiler by downloading an example project.

Finally, here is an example of a simplified Makefile, based around compiling the source file "led.c" into the binary file "led.bin", which can be flashed:


CFLAGS = -c -Os -ffunction-sections -fdata-sections -I. -Icommon
LDFLAGS = -Wl,--gc-sections -nostartfiles

led.bin: led.elf
	$(TOOL_PATH)/rl78-elf-objcopy -O binary $^ $@

led.elf: led.o crt0.o
	$(TOOL_PATH)/rl78-elf-gcc $(LDFLAGS) -T common/rl78_R5F100GJAFB.ld $^ -o $@

crt0.o: common/crt0.S
	$(TOOL_PATH)/rl78-elf-gcc $(CFLAGS) $^ -o $@

led.o: led.c
	$(TOOL_PATH)/rl78-elf-gcc $(CFLAGS) $^ -o $@

.PHONY: clean
	rm -f *.o *.elf *.bin

Here is the led.c example, which I have mostly just copied from the Internet and not written myself:

#include <iodefine.h>
#include <iodefine_ext.h>

#define LED_CYAN_PIN    PM1.BIT.bit7
#define LED_MAGENTA_PIN PM5.BIT.bit1
#define LED_YELLOW_PIN  PM5.BIT.bit0
#define LED_CYAN        P1.BIT.bit7
#define LED_MAGENTA     P5.BIT.bit1
#define LED_YELLOW      P5.BIT.bit0

void wdti_handler(void)

void it_handler(void)
    LED_CYAN    ^= 1;
    LED_MAGENTA = 0;
    LED_YELLOW  ^= 1;

void main(void)

  LED_CYAN_PIN    = 0;

  LED_CYAN    = 0;
  LED_YELLOW  = 0;

  /* Setup clocks */                                      
  CMC.cmc = 0x11U; /* Enable XT1, disable X1 */ = 0x80U; /* Start XT1 */
  CKC.ckc = 0x00U;

  /* Interval timer */
  OSMC.osmc = 0x80U;  /* Supply fsub to Interval Timer */
  RTCEN = 1;
  ITMK  = 1; /* Disable interrupt... */
  ITPR0 = 0; /* High pri... */
  ITPR1 = 0;
  ITMC.itmc = 0x8FFFU; /* 270ms... */
  ITIF = 0; /* interrupt request flag... */
  ITMK = 0; /* Enable interrupt... */

  asm("ei"); /* Enable interrupts */

    asm("stop"); /* STOP mode. */

Topic: Configuration, by Kjetil @ 23/09-2017, Article Link

Slackware 14.2 on a USB-stick

It's exactly 10 years since the first post on this website.

Anyway, I discovered that installing Slackware on a USB-stick wasn't as easy as it was last time in 2012, and those instructions are no longer valid for newer versions like 14.2. The main problem is that the kernel no longer contains built-in support for USB mass storage and the extended file systems.

Here are the updated steps:

1. Boot a host machine with the original Slackware DVD.

2. Insert the USB-stick into the host machine and use fdisk to create a large single Linux (0x83) partition on it.

3. Run the Slackware setup program and choose the newly created USB-stick partition as the target, with an ext4 file system.

4. Near the end of the setup process, skip the step involving installation of the LILO boot loader!

5. Unplug the USB-stick and put it into another fully functional Slackware 14.2 box.

6. Mount and chroot into the USB-stick filesystem. (In my case this was at /dev/sdc1 mounted on /mnt/sdc1):

mount /mnt/sdc1
mount -o bind /proc /mnt/sdc1/proc
mount -o bind /sys /mnt/sdc1/sys
mount -o bind /dev /mnt/sdc1/dev
chroot /mnt/sdc1

6. Configure and update the Linux kernel, by following these steps:

cd /usr/src/linux
make menuconfig # Do the necessary changes as described below.
make modules
make modules_install
make install # Will fail on LILO install, but just ignore this since kernel image is still copied to /boot.

In menuconfig, make sure to change these from module to built-in:
* xHCI HCD (USB 3.0) support
* EHCI HCD (USB 2.0) support
* USB Mass Storage support
* Second extended fs support
* The Extended 3 (ext3) filesystem
* The Extended 4 (ext4) filesystem

7. Modify /etc/fstab on the USB-stick and make sure that the root file system uses the correct partition device on the target machine. (e.g. /dev/sdb1)

8. Create the file /boot/extlinux.conf on the USB-stick and input contents similar to this:

default Linux
prompt 1
timeout 100
label Linux
  kernel vmlinuz
  append root=/dev/sdb1 rootwait vga=normal vt.default_utf8=0

8. Exit the chroot environment:

umount /mnt/sdc1/proc
umount /mnt/sdc1/sys
umount /mnt/sdc1/dev

9. Install the boot loader on the USB-stick with the "extlinux" command like this: "extlinux -i /mnt/sdc1/boot". (Replace /mnt/sdc1 with correct mount point if necessary.)

10. Unmount the USB-stick and overwrite the master boot record on it with a command like this: "cat /usr/share/syslinux/mbr.bin > /dev/sdc". (Replace /dev/sdc with correct device if necessary.)

Topic: Configuration, by Kjetil @ 28/07-2017, Article Link

4096 Byte Sector Mount

Using a large hard disk of over 2TB on a USB enclosure, and then attempting to use the same disk on a regular Serial ATA interface may not work at all. The issue lies in the USB enclosure using some trick to convert 512 byte sectors to 4096 byte sectors when displaying the disk to the OS. I present here the solution to mount and read such a disk with Linux.

I have used a 4TB disk with a small partition to experiment.
When the disk is connected to the USB enclosure, Linux reports it with 4096-byte logical blocks:

usb 2-1.2: new high speed USB device using ehci_hcd and address 4
usb 2-1.2: New USB device found, idVendor=174c, idProduct=5106
usb 2-1.2: New USB device strings: Mfr=2, Product=3, SerialNumber=1
usb 2-1.2: Product: USB to ATA/ATAPI bridge
usb 2-1.2: Manufacturer: Asmedia
usb 2-1.2: SerialNumber: 30700000000000000A22
scsi9 : usb-storage 2-1.2:1.0
scsi 9:0:0:0: Direct-Access     WDC WD40 EZRZ-00WN9B0     80.0 PQ: 0 ANSI: 0
sd 9:0:0:0: Attached scsi generic sg6 type 0
sd 9:0:0:0: [sdf] 976754646 4096-byte logical blocks: (4.00 TB/3.63 TiB)
sd 9:0:0:0: [sdf] Write Protect is off
sd 9:0:0:0: [sdf] Mode Sense: 23 00 00 00
sd 9:0:0:0: [sdf] Assuming drive cache: write through

But when connected on the SATA interface, Linux reports it with 512-byte logical blocks:

ata6.00: ATA-9: WDC WD40EZRZ-00WN9B0, 80.00A80, max UDMA/133
ata6.00: 7814037168 sectors, multi 0: LBA48 NCQ (depth 0/32)
ata6.00: configured for UDMA/100
ata6: EH complete
scsi 5:0:0:0: Direct-Access     ATA      WDC WD40EZRZ-00W 0A80 PQ: 0 ANSI: 5
sd 5:0:0:0: [sdd] 7814037168 512-byte logical blocks: (4.00 TB/3.64 TiB)
sd 5:0:0:0: [sdd] 4096-byte physical blocks
sd 5:0:0:0: [sdd] Write Protect is off
sd 5:0:0:0: [sdd] Mode Sense: 00 3a 00 00
sd 5:0:0:0: [sdd] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA

Attempting to mount the partition directly will fail because of the sector (block) mismatch:

# mount /dev/sdd1 /mnt/sdd1
mount: /dev/sdd1 is write-protected, mounting read-only
NTFS signature is missing.
Failed to mount '/dev/sdd1': Invalid argument
The device '/dev/sdd1' doesn't seem to have a valid NTFS.
Maybe the wrong device is used? Or the whole disk instead of a
partition (e.g. /dev/sda, not /dev/sda1)? Or the other way around?

To get around this, we need to use a loopback device, but first some calculations must be done, based on the start and end sectors of the partition. This data can be gotten with e.g. fdisk:

# fdisk -l /dev/sdd
Disk /dev/sdd: 3.7 TiB, 4000787030016 bytes, 7814037168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: dos
Disk identifier: 0x52205024

Device     Boot Start   End Sectors  Size Id Type
/dev/sdd1         256 25855   25600 12.5M 83 Linux

Using 4096 byte sectors, we get the start sector at byte: 4096 * 256 = 1048576
And a byte size of: 4096 * 25600 = 104857600

Using these numbers, the partition can be mounted like so:

losetup --verbose --offset 1048576 --sizelimit 104857600 /dev/loop0 /dev/sdd
mount /dev/loop0 /mnt/loop

And after use, unmounted like so:

umount /mnt/loop
losetup -d /dev/loop0

Topic: Configuration, by Kjetil @ 17/05-2017, Article Link

Libvirt for KVM Guest

Referring to the Buildroot-based KVM Guest earlier, here is a way to set it up using libvirt: The virtualization API. Using libvirt instead of just QEMU gives additional flexibility, like easier pinning of physical CPUs to VCPUs and so on.

This setup also assumes that the root filesystem is already laid out in /tmp/kvm_guest, to be shared using the 9P protocol. Due to the fact that many files in the root filesystem are owned by root, the whole virtualization also needs to be run as root, or else the host will not allow access to the shared files.

Here is the XML configuration file for libvirt, some paths and the UUID may need to be changed. I named it "kvm_guest.xml":

<domain type='kvm' xmlns:qemu=''>
  <memory unit='KiB'>131072</memory>
  <currentMemory unit='KiB'>131072</currentMemory>
  <vcpu placement='static'>1</vcpu>
    <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
    <cmdline>root=/dev/root rw rootfstype=9p rootflags=trans=virtio console=hvc0</cmdline>
  <cpu mode='custom' match='exact'>
    <model fallback='allow'>kvm64</model>
  <clock offset='utc'/>
    <controller type='pci' index='0' model='pci-root'/>
    <memballoon model='none'/>
    <console type='pty'>
      <target type='virtio' port='0'/>
    <interface type='ethernet'>
      <mac address='00:00:de:ad:be:ef'/>
      <target dev='tap0'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    <qemu:arg value='-chardev'/>
    <qemu:arg value='stdio,id=char0'/>
    <qemu:arg value='-device'/>
    <qemu:arg value='virtio-serial'/>
    <qemu:arg value='-device'/>
    <qemu:arg value='virtconsole,chardev=char0'/>
    <qemu:arg value='-fsdev'/>
    <qemu:arg value='local,id=fs0,path=/tmp/kvm_guest,security_model=none'/>
    <qemu:arg value='-device'/>
    <qemu:arg value='virtio-9p-pci,fsdev=fs0,mount_tag=/dev/root'/>

It can then be started and accessed like so:

sudo /etc/rc.d/rc.libvirt start
sudo virsh create kvm_guest.xml
sudo virsh console kvm_guest

The rc.libvirt script is possibly Slackware specific, your system may have a different way to start the libvirt daemons.

Topic: Configuration, by Kjetil @ 04/03-2017, Article Link

Bluray on Slackware

Here's information on how I was able to play encrypted Bluray disks on Slackware using a custom MPlayer.

This statement from the MPlayer homepage is vital:
"MPlayer does support encrypted BluRay playback, though not all steps are handled by MPlayer itself. The two alternative methods use the URL schemes bd:// (always supports decryption, but you need the key for each and every disk in ~/.dvdcss/KEYDB.cfg and only works well with very simple BluRays, similar to dvd:// vs. dvdnav://) and br:// (uses libbluray and should support the same as VideoLAN in the link below but that is untested)."

I only got it to work with the "br://" scheme. The MPlayer package that follows the default Slackware installation does NOT link to libbluray, so I had to download and compile it manually, and then use this separate MPlayer binary for Bluray playback only. Prior to this, I had already installed libbluray, libbdplus and libaacs on the system.

I launch it with the following helper script:

if ! fgrep /mnt/cdrom /proc/mounts > /dev/null; then
  mount /mnt/cdrom || exit 1

# Enable debugging info for libbluray:

# NOTE: Set the BLURAY_TITLE variable to change titles.
# NOTE: Use -chapter <id> argument to change chapters.
exec ~/opt/mplayer-bluray-bin br://$BLURAY_TITLE//mnt/cdrom "$@"

Finally, you will need to have an updated KEYDB.cfg file at ~/.config/aacs/KEYDB.cfg and hope that the VUK (Volume Unique Key) has been discovered for your Bluray disc.

Topic: Configuration, by Kjetil @ 18/02-2017, Article Link

Buildroot for KVM Guest

Here is a set of configuration files to quickly build a minimal KVM-based hypervisor guest with Buildroot. It attempts to use mostly VirtIO paravirtualization to maximize performance. This includes virtual console, virtual network and virtualized filesystem with the 9P protocol.

These files are based on version 2016.11 of Buildroot, gotten from

Main Buildroot configuration file, should be stored as "configs/kvm_guest_defconfig" in the Buildroot structure:

BR2_TARGET_GENERIC_ISSUE="Welcome to Virtualized KVM Guest"
# BR2_TARGET_ROOTFS_TAR is not set

Linux kernel configuration, should be stored as "board/kvm_guest/linux-4.4.config":


Once the files are in place, the build can be performed with:

make kvm_guest_defconfig

Here is a helper script to unpack the root filesystem and launch the whole thing under QEMU:


if [ `id -u` != "0" ]; then
  echo "Must be root!"
  exit 1

cd `dirname "$0"`

if [ ! -d /tmp/kvm_guest ]; then
  mkdir -p /tmp/kvm_guest
  mkdir -p /tmp/kvm_guest_loop
  mount -o loop output/images/rootfs.ext4 /tmp/kvm_guest_loop
  cp -a /tmp/kvm_guest_loop/* /tmp/kvm_guest
  umount /tmp/kvm_guest_loop
  rmdir /tmp/kvm_guest_loop

qemu-system-x86_64 \
  -enable-kvm \
  -cpu host \
  -m 128 \
  -kernel output/images/bzImage \
  -serial none \
  -display none \
  -monitor none \
  -chardev stdio,id=char0 \
  -device virtio-serial \
  -device virtconsole,chardev=char0 \
  -fsdev local,id=fs0,path=/tmp/kvm_guest,security_model=none \
  -device virtio-9p-pci,fsdev=fs0,mount_tag=/dev/root \
  -net nic,macaddr=00:00:de:ad:be:ef,model=virtio,vlan=1 \
  -net tap,ifname=tap0,vlan=1,script=no,downscript=no \
  -append "root=/dev/root rw rootfstype=9p rootflags=trans=virtio console=hvc0"

Topic: Configuration, by Kjetil @ 14/01-2017, Article Link

Steam on Slackware 14.2

Slackware 14.2 was recently released, so I decided to attempt a Steam installation on it, which succeeded in the end.

Here are the steps I followed:
1) Download and install the 64-bit version of Slackware 14.2. You can get it here:
2) Setup "multilib", so the system is able to run 32-bit programs. Use this guide:
3) Install OpenAL, which is a dependency for Steam. I recommend to use SlackBuilds:
4) Install Steam. Also easy with SlackBuilds:
5) Install a proprietary graphics driver, for better OpenGL performance.

It is important to install a 64-bit base system because some of the games on Steam are in fact only 64-bit. However, Steam itself and certain other games are only 32-bit, hence the need for multilib. I use an nVidia graphics card, and this had to be installed AFTER multilib had been set up, so I recommend doing the same.

I have done only minor testing so far, but I am still a bit sceptical about the OpenAL dependency. It seems that some games bundle their own OpenAL libraries, so they aren't always needed. It is also possible that a 32-bit version of the OpenAL library needs to be installed for certain stuff to work.

Topic: Configuration, by Kjetil @ 07/07-2016, Article Link

Japanese Input on Slackware

I have been looking for the least intrusive way to enter Japanese text on my main Slackware box, without messing with the rest of the system. I am used to running with $LANG set to "en_US", which is the Slackware default. Then using Latin-1/ISO-8859-1 as the encoding in the filesystem and so on.

The goal is enter Japanese text into Mozilla Firefox and (G)Vim, under the Fluxbox window manager.

Fortunately, those two programs are GTK-based and starts SCIM automatically as long as certain environment variables are set. Here is how it's done:

export LC_CTYPE='ja_JP.utf8'
export GTK_IM_MODULE="scim-bridge"

In that Firefox session, Control + Space can be used to switch between input methods.

Topic: Configuration, by Kjetil @ 11/12-2015, Article Link

Buildroot for 486

Here are the steps I used in order to get a brand new Linux version 4 kernel running on an old 486 DX computer. The setup is based on the Buildroot system for making embedded systems. The end result is the kernel with a very simple BusyBox based userland.

All the steps are based on the 2015.05 version of Buildroot, gotten from

Get the three necessary configuration files from Here.

First of all copy the configuration files into Buildroot and prepare it:

mkdir board/i486
cp i486_defconfig configs/
cp linux-4.0.4-i486.defconfig board/i486
cp extlinux.conf board/i486
make i486_defconfig

For any further changes to the configuration, use:

make menuconfig
make linux-menuconfig

Then build!:


I had to use extlinux as a bootloader, since neither syslinux nor GRUB worked correctly in my case. A Compact Flash card was inserted as /dev/sdd, and setup like this:

fdisk /dev/sdd # One primary partition, active, uses all space.
sudo cat /usr/share/syslinux/mbr.bin > /dev/sdd
mkfs.ext2 /dev/sdd1

Once the CF card is prepared, relative from the Buildroot folder, the files can be copied over:

mount /mnt/sdd1
sudo tar xf ./output/images/rootfs.tar -C /mnt/sdd1
sudo mkdir /mnt/sdd1/boot
sudo cp ./output/images/bzImage /mnt/sdd1/boot/
sudo cp ./board/i486/extlinux.conf /mnt/sdd1/boot/
sudo extlinux -i /mnt/sdd1/boot
umount /mnt/sdd1

The card is then ready to be used.

Topic: Configuration, by Kjetil @ 01/08-2015, Article Link

Static PDCurses with MinGW

Statically linking a curses program on Windows is not as easy as it sounds. The main problem is that the PDCurses distributions that you'll find on only contains the dynamic (DLL) library, not the static library. To get the static library, the only option seems to be to build PDCurses yourself from source.

I will try to explain. Before starting, make sure that you have installed MinGW and remembered to include the "make" program as well. Then proceed to download the PDCurses source and unpack it. Open a Windows command window and build it like this:

cd PDCurses-3.4\win32\
make -f mingwin32.mak

(I had to set the PATH manually, or else I kept getting problems with a missing reference to libgmp-10.dll.)

The quick and dirty way to now link your curses program is to just copy the resulting pdcurses.a file (from the win32 directory) and the curses.h file (from the root PDCurses directory) to where you got your source and compile like this:

gcc -o program program.c -I. pdcurses.a

You can verify that the program is indeed statically linked by using objdump like this:

objdump -p program.exe

There should NOT be any reference to a PDCurses.dll file. If there is, well, then the program is still dynamically linked.

Sure a statically linked program is much larger, but it's much easier to distribute, since I doubt many people have the PDCurses.dll file on their Windows computers.

Topic: Configuration, by Kjetil @ 01/01-2014, Article Link

DOS Game Cheats

This is article is a response to the one about binary patching tools that I made earlier. The reason these tools were created in the first place, was to have an easy way to specify cheat patches for some old DOS games. Below you will find "infinite lives" patches for three games that I managed to run in a debugger and crack open. Use the binary patching tool to read these and apply the changes.

Blues Brothers:

# Apply to "1.EXE", Original md5sum: 4317d3fa97fff52b5785cc31d3110ece

# Get infinite lives:
000062cd: 90 90 90 90
0000a061: 90 90 90 90

# Remove infinite lives:
#000062cd: 26 fe 4f 61
#0000a061: 26 80 7f 61

Fantasy World Dizzy:

# Apply to "DIZZY.EXE", Original md5sum: d614048d262945dccf069fe968f747cb

# Get infinite lives:
00006038: 90 90 90 90 90 90

# Remove infinite lives:
#00006038: fe 0e 84 4d 78 5e

Soccer Kid:

# Apply to "KID.EXE", Original md5sum: b5c443e0c8a9099706f22b952b68b2c1

# Get infinite lives:
00001257: 90 90

# Remove infinite lives:
#00001257: 2c 01

Topic: Configuration, by Kjetil @ 01/07-2013, Article Link

Commodore 64 RS-232 Interface

By using a TTL-level RS-232 to USB converter, like this one, it was surprisingly easy to connect the Commodore 64 to a Linux box. I read somewhere that an inverter was required on the RxD/TxD signals, but during my testing with this converter, that was not necessary. This means you only need wires, and no additional electronics!

The RS-232 interface is found on the C64's user port, and the wiring is as follows:

C64 Signal:   C64 User Port:   Converter:
Ground        A + N            Gnd
RxD           B + C            TxD
TxD           M                RxD 

The best terminal program for the Commodore 64 is NovaTerm, since it supports VT102/ANSI emulation and such.

Assuming the converter is connected to a Linux box and detected as /dev/ttyUSB0, the connection can be tested like this at 1200 baud, which is the most common:

screen /dev/ttyUSB0 1200

To provide a login prompt at 1200 baud and supporting ANSI escape characters, something like this can be run as root:

agetty -L ttyUSB0 1200 ansi

Here's a "screenshot" of NovaTerm 9.6c connected to a Linux box and running top:

Screenshot of top via NovaTerm.

Topic: Configuration, by Kjetil @ 19/01-2013, Article Link

Easy Kernel Upgrade

Here are the steps needed to compile a new Linux kernel (2.6.x or 3.x) on recent Slackware versions, based on the configuration of the old (running) kernel. Assuming a new kernel has been downloaded and unpacked into /usr/src, proceed like this:

# Become the super user:

# Move into the standard source(s) location:
cd /usr/src

# Remove old symlink:
rm linux

# Create new symlink:
# (Replace X.X.XX with whatever was downloaded.)
ln -s linux-X.X.XX linux

# Move into new kernel:
cd linux

# Copy old kernel configuration from running kernel:
cat /proc/config.gz | gunzip > .config

# Use old .config to make a new one:
# (Just press enter on all the questions to use defaults.)
make oldconfig

# Compile the kernel:

# Compile the modules:
make modules

# Install the modules:
# (Will be placed under /lib/modules/X.X.XX/)
make modules_install

# Install the kernel:
# (Everything is done automatically, including update of LILO!)
make install

# Reboot to load the new kernel:

Topic: Configuration, by Kjetil @ 26/12-2012, Article Link

SGI Indigo2 Network Boot

I have gotten hold of an old Silicon Graphics Indigo2, a beautiful piece of machinery. While attempting to recover the root password, the harddisk failed. But by some extreme luck, I managed to create a copy of the root filesystem before this happened. By using an SGI manual and lots of trial and error, I finally managed to boot IRIX and run the entire thing over the network from some Linux boxes. I have written down everything required to configure this in a LaTeX typesetted PDF document here.

Here's a photo of the desktop of IRIX 5.3, the UNIX used by SGI:

SGI Indigo2 Desktop Photo.

And in case you are wondering, here is a dump of the "hinv" command, showing the hardware specs:

Iris Audio Processor: version A2 revision 1.1.0
1 200 MHZ IP22 Processor
FPU: MIPS R4010 Floating Point Chip Revision: 0.0
CPU: MIPS R4400 Processor Chip Revision: 6.0
On-board serial ports: 2
On-board bi-directional parallel port
Data cache size: 16 Kbytes
Instruction cache size: 16 Kbytes
Secondary unified instruction/data cache size: 1 Mbyte
Main memory size: 192 Mbytes
EISA bus: adapter 0
Integral Ethernet: ec0, version 1
Integral SCSI controller 1: Version WD33C93B, revision D
Integral SCSI controller 0: Version WD33C93B, revision D
Disk drive / removable media: unit 2 on SCSI controller 0
Graphics board: GU1-Extreme

Topic: Configuration, by Kjetil @ 08/09-2012, Article Link

Easy File Selection

Here is a trick to let you easily select a file "graphically" for a console based application, when launched from a menu in a window manager. It uses Python and two modules that are delivered with the standard distribution.

The following example launches the "mplayer" media player with a selected file:


import subprocess
import tkFileDialog

file = tkFileDialog.askopenfilename()
if len(file) > 0:["mplayer", file])

Instead of launching the application directly, create a script like this and point the menu entry towards that instead.

Topic: Configuration, by Kjetil @ 04/04-2012, Article Link

Slackware on a USB-stick

I present here the steps I used to get Slackware Linux installed on a USB-stick. These are the steps that worked for me, and I cannot guarantee that they will work for everyone, but it will give some pointers that may be helpful anyway. Also, I assume the reader has some familiarity with Linux already, so the steps described may be somewhat high-level. The motivation behind this was to get Slackware running on a laptop without touching the contents of the original hard drive in it.

Also note that this particular procedure ties the USB-stick to a specific machine, or at least a specific device layout. It is necessary to know (beforehand) what device identifier will be assigned for the root partition on the particular machine where the USB-stick will be used. In my case this was /dev/sdb1, so this will be used in description below.

I divided the procedure into three major steps with some sub-steps each, here we go:

1. Install Slackware the usual way, but with the USB-stick as the root file system.
1.1. Boot a host machine with the original Slackware DVD.
1.2. Insert the USB-stick into the host machine and use fdisk to create a large single Linux (0x83) partition on it.
1.3. Run the Slackware setup program and choose the newly created USB-stick partition as the target, with an ext2 file system.
1.4. Near the end of the setup process, skip the step involving installation of the LILO boot loader!

2. Install the special "extlinux" boot loader on the USB-stick.
2.1. Insert the USB-stick into a host machine running the necessary Linux software, and mount it. (In my case this was /dev/sdc1 mounted on /mnt/usb.)
2.2. Modify /etc/fstab on the USB-stick and make sure that the root filesystem uses the correct partition device on the target machine. (e.g. /dev/sdb1)
2.3. Create the file /boot/extlinux.conf on the USB-stick and input contents similar to this:

default Linux
prompt 1
timeout 100
label Linux
  kernel vmlinuz
  append root=/dev/sdb1 rootdelay=10 vga=normal vt.default_utf8=0

The crucial configuration here is the "rootdelay" parameter to the kernel!
2.4. Install the boot loader on the USB-stick with the "extlinux" command like this example: "extlinux -i /mnt/usb/boot". (Replace /mnt/usb with correct mount point if necessary.)
2.5. Unmount the USB-stick and overwrite the master boot record on it with a command like this: "cat /usr/lib/syslinux/mbr.bin > /dev/sdc". (Replace /dev/sdc with correct device if necessary.)

3. Boot the target machine with the USB-stick.
3.1. For some reason I got a lot of "ext2_lookup" error messages while booting, and I am not really sure what these where caused by. But they can be fixed by running "e2fsck -y -v /dev/sdb1" in system recovery mode on the target machine.

Topic: Configuration, by Kjetil @ 12/02-2012, Article Link

TCP proxy with Netcat

Here is an interesting way to setup a generic TCP proxy using the netcat tool. What makes this method interesting is that traffic is not simply forwarded, but also sent back the other way. In order to do this, we require a two Unix pipes. One is created using the shell's own mechanism, and the other one is created manually as a named pipe (also known as a fifo).

Imagine we want to connect with telnet to some remote host, through the proxy like this:
local-host <-> proxy-host <-> remote-host.

On the proxy-host, enter this on the shell:

mkfifo /tmp/backpipe
nc -l -p 31337 < /tmp/backpipe | nc remote-host 23 > /tmp/backpipe

On the local-host, you can now enter this...:

telnet proxy-host 31337
   reach port 23 on the remote host, and the traffic will flow in both directions.

Topic: Configuration, by Kjetil @ 18/07-2010, Article Link

Send and Receive files with Netcat

Here is a very simple way to send single files over the network, from one host to another. All you need is the netcat tool (also known as "nc" on the command line) and a couple of shell scripts. The transfer method is like FTP (over a straight TCP connection), but without the control channel.

Sender script:

if [ -z "$1" ]; then
  echo "Usage: $0 <file to send>"
  exit 1
nc $REMOTE_HOST 10001 -q 1 < "$1"

Receiver script:

if [ -z "$1" ]; then
  echo "Usage: $0 <file to receive>"
  exit 1
if [ -e "$1" ]; then
  echo "error: file exists."
  exit 1
  nc -p 10001 -l > "$1"

In these examples, I am using a static host as the receiver ($REMOTE_HOST), so I don't need to specify it all the time. I have also decided on a static port number (10001) to use on both ends. The port number should probably be over 1024, so a normal user can create a listening socket on it. Also note that the receiver script always needs to be started before the sender script, if not, the connection will be refused.

Topic: Configuration, by Kjetil @ 29/11-2009, Article Link

Aspire One D250 Tweaks

I recently bought a new netbook, an Acer Aspire One D250 (HD version). Unfortunately, it was not possible to get it with Linux pre-installed, but I quickly re-formatted the hard-drive and installed Slackware 13.0. As usual with new hardware on Linux, some devices were not supported out of the box. After some tweaking however, I got it all up and running.

There were problems with both the ethernet and the wireless network interfaces, and also with the built in microphone controlled by the sound-card. Here is the "lspci -nn" information of the affected hardware devices:

0:1b.0 Audio device [0403]: Intel Corporation 82801G (ICH7 Family) High Definition Audio Controller [8086:27d8] (rev 02)
01:00.0 Network controller [0280]: Broadcom Corporation BCM4312 802.11b/g [14e4:4315] (rev 01)
03:00.0 Ethernet controller [0200]: Attansic Technology Corp. Atheros AR8132 / L1c Gigabit Ethernet Adapter [1969:1062] (rev c0)

The Wi-Fi interface can be fixed by installing the proprietary driver from Broadcom, instead of using the open source "b43" driver. (The b43 driver did not support this particular chip-set in the beginning of September when I installed everything, but this may have changed now.) I used the "hybrid-portsrc-x86_32-v5_10_91_9.tar.gz" driver with the "" patch, and it has so far worked fine. I used the following steps to install it (some of the same information can also be found in the driver's readme file):

patch -p1 < patch_2.6.29_kernels
make -C /lib/modules/ M=`pwd` clean
make -C /lib/modules/ M=`pwd`
sudo cp wl.ko /lib/modules/
sudo depmod

The ethernet interface suffers from strange bugs in the driver (atl1c) shipped with the default kernel ( Sometimes the chip itself disappears from the PCI bus, and does not show up until a complete power on/off has been done. The "ifconfig" command also shows crazy RX and TX values for the interface. I had to blacklist the kernel driver (by adding a file with "blacklist atl1c in the /etc/modprobe.d/ directory) and install the proprietary Atheros driver to fix it. I used the "AR81Family-linux-v1.0.0.10.tar.gz" file, which makes a new kernel module/driver named "atl1e".

The final thing I had issues with was the internal microphone, but it was fixed simply by updating the ALSA driver and library to version 1.0.21.

Topic: Configuration, by Kjetil @ 03/10-2009, Article Link

Amilo L7300 Tweaks

I have a Fujitsu Siemens Amilo L7300 laptop with Slackware Linux (kernel version 2.6.27) installed on it. As usual with laptop hardware, there are quirks and other special things, mostly because of the complex ACPI system.

In the /etc/rc.d/rc.local script that runs at start-up, I have put some commands that helps with some of the issues with this particular laptop. Hopefully this information can be of use for others also.

One issue is that the fans are turned on at power on, but they are never turned off. Normally they should be activated on demand when temperature rises and then deactivated as the temperature drops. They need to be turned on, then off and then on again manually to fix them.

Another issue is an annoying bug with the keyboard and touch-pad. Around 10% of the times the system has booted, they do not work at all, no reaction. The root cause seems to be the i8042 controller, but luckily it can be "reset".

Finally there is custom keyboard button (with a fan or atomic symbol on it) that is not recognised by the kernel. There is a command to map it, so that it can be used in X afterwards.

Here is parts of the rc.local script:


echo "Reset ACPI fan status."
echo 3 >> /proc/acpi/fan/FN1/state
echo 3 >> /proc/acpi/fan/FN2/state
sleep 1
echo 0 >> /proc/acpi/fan/FN1/state
echo 0 >> /proc/acpi/fan/FN2/state
sleep 1
echo 3 >> /proc/acpi/fan/FN1/state
echo 3 >> /proc/acpi/fan/FN2/state

echo "Reset i8042 keyboard and touch-pad."
echo -n "i8042" > /sys/bus/platform/drivers/i8042/unbind 
sleep 1
echo -n "i8042" > /sys/bus/platform/drivers/i8042/bind 

echo "Assign keycode for extra key on keyboard."
/usr/bin/setkeycodes 6d 120

Topic: Configuration, by Kjetil @ 01/09-2009, Article Link

X Terminal Directory Hack

When working under the X Window System environment with multiple terminals opened, I usually work in the same directory on the file system. Instead of manually changing directory every time a new terminal is opened, I had an idea to do it with shell functions like this:

function mk { pwd | xclip; }
function rt { cd `xclip -o`; }

These functions will use the X clipboard to store and retrieve the current directory using the xclip tool. This way, the current directory can be marked (mk) in the terminal currently in use and retrieved (rt) in the new terminal afterwards.

Topic: Configuration, by Kjetil @ 17/08-2009, Article Link

Deus Ex vblank fix.

Deus Ex is one of those games that seems to work flawlessly under Wine in Linux, except for this issue I will mention below.

If you play Deus Ex on a fast machine, you may experience that the audio dialogs cuts off to early. According to information lying around on the Internet, this seems to be a generic bug/weakness in the Unreal engine, and apparently affects all platforms(?). But it can be fixed by forcing sync to vblank, which slows down the (too fast) frame rate. There does not seem to be any way of doing this from the menus in Deus Ex, but it can be done by configuring it in the graphics driver.

On Linux (at least with the proprietary Nvidia drivers) there is an easy way to setup sync to vblank automatically, by setting an environment variable.

You can just wrap/replace the actual binary with a small shell script like this:

export __GL_SYNC_TO_VBLANK=1
exec /path/to/deusex

Topic: Configuration, by Kjetil @ 06/03-2009, Article Link

OpenTTD music files in other formats.

OpenTTD is a multi-platform clone of the classic game Transport Tycoon.

Normally, the original midi files are used, and requires a midi player to play the music. The default midi player is "timidity", which produces excellent sound, but seems to use a lot of CPU, so I want to use something else.

It is actually possible to use other formats (and players) than midi, due to the way the music system is currently implemented in OpenTTD. When a music track is started; OpenTTD fork()s and starts the music player as a separate process with the music filename as an argument.

I have successfully managed to use "ogg123" (part of the vorbis-tools package from to play the music files, by converting all the midi files to Ogg Vorbis format and perform a single configuration change. In the configuration file "openttd.cfg", there is a section named "[music]", where a option named "extmidi" resides. Simply change the option value to "ogg123" or the name of the player you want to use. When converting the midi files to a different format, make sure the filenames of the new files are exactly the same as the old ones.

I tested this with OpenTTD version 0.5.3 under Linux.

Topic: Configuration, by Kjetil @ 31/12-2007, Article Link

TrueType fonts in Linux.

The GIMP was used to create the header logo on this site, and I used an old TrueType font I had lying around in it. But anyway, how does one install such fonts under Linux?

The answer is simple. Just copy the font file (as root) into the fonts directory with a command like this:
cp file.ttf /usr/share/fonts/corefonts/

Topic: Configuration, by Kjetil @ 29/07-2007, Article Link