Friday 17 March 2023

Doom Interrupted My Naked Lunch

David Cronenbourg's Naked Lunch is a strange film, based on William S. Burroughs' strange book of the same name. Much of it is depicted from the point of view of someone whose grip on reality is very tenuous, and it has the effect of leaving the viewer unsure of reality too.

I saw it for the first time in about twenty years at the Prince Charles Cinema just off Leicester Square, last Tuesday evening.

What made it more strange this time was that, about half-way through, in a generally quiet shot, I suddenly thought I heard an imp from the original 1993 Doom dying. It seemed to stand out very clearly but, this being a cinema screening, I couldn't rewind the film to see if I'd imagined it.

The game is from 1993 and the film is from 1991. Could the game have lifted from the film, or might they have used the same sound effects?

Here's the shot from Naked Lunch, in which the sound that I noticed can be heard at the end:

Meanwhile the imp death in question is here:

It is a match!

Doom Wiki's list of Origins of Doom Sounds says the imp death sounds are actually recordings of camel vocalisations taken from a Lucasfilm sound library, and Naked Lunch must have used the same one.

It was a strange thing to notice in the cinema.

Saturday 14 January 2023

Configuring DOSBox for Doom, the Hard Way

Background

I've been playing Doom (the original 1993 version, of course) using DOSBox to emulate a PC on my old MacBook Pro.

Doom does allow use of the mouse to turn and to move forwards and backwards. With the mouse it's hard to do either of those things independently, though. Standing on the edge of a precipice above some lava or radioactive slime, it's difficult to aim at the imps on the other side without inadvertently stepping off the edge.

This is particularly evident in places like the Toxin Refinery (E1M3) whose architects' lax attitude to health and safety has resulted in a worrying absence of guard rails:


Introduction

I want to use the mouse to turn but not to move forwards and backwards. (The keyboard is just fine for the latter.)

Doom does not have an option for this. DOSBox, similarly, has only one setting for mouse sensitivity. Someone did commit a change that allows setting the X and Y sensitivities independently - just what I want! - but that change didn't make it into the latest DOSBox release, despite being slightly earlier than that release.

What to do?

Possibilities

I could:

  1. Learn to live with the existing mouse behaviour and accept the occasional lava bath.
  2. Wait an indeterminate amount of time for people who owe me nothing to decide to release a new version of DOSBox, which would probably include the patch that does what I want.
  3. Download the DOSBox source code from SourceForge and try to build my own DOSBox that includes the behaviour I want.
  4. Abandon this nostalgia trip entirely and do something useful.
  5. Work out where the existing DOSBox binary interacts with the OS mouse APIs and patch it to do what I want.

I considered all of these. Building my own DOSBox sounded doable, but I was soon discouraged by having to install autoconf and automake first, then finding I need a SDL library against which to link it. I seemed to be needing to install quite a large amount of sledgehammer to crack this nut and I wondered whether I'd even end up with a working program.

Meanwhile, looking at the DOSBox commit that separates the X and Y sensitivities got me thinking: patching the binary might actually be simpler. There ought to be only one place in the code that needs to deal with the mouse and I would expect it to be marked by a call to an OS function whose name contains the word 'Mouse'.

I have the free version of IDA installed already. It's too tempting not to give this try.

Devising a Patch

Let's see what IDA tells us about the DOSBox executable.

The program only uses to three mouse-related symbols, and only the second of those looks relevant to our purposes:


The symbol itself is just a thunk:


But let's see what refers to that thunk:


There's only one cross reference. That's promising:


This is it:


The only point at which this program asks the operating system about mouse movement is in this block. Encouragingly, the six instructions immediately after that call seem to be doing the following:

  • Increment the value at rbp-68h by the value at rbp-38h;
  • Increment the value at rbp-5Ch by the value at rbp-7Ch.

That looks a lot like it's accumulating the X and Y deltas returned by the CGGetLastMouseDelta() function into absolute X and Y positions.

If so, preventing the second of those addition instructions from happening should stop DOSBox from noticing any movement of the mouse on the Y axis, while still letting movement on the X axis have its normal effect.

The second addition is at byte 0x233fb9 in the file, which corresponds to virtual address 0x100232fb9 when loaded into memory.

Applying the Patch

In fact, now that we know what logical address we are looking for, we can find this same sequence of instructions using plain old objdump:

$ objdump -macho -disassemble -x86-asm-syntax=intel DOSBox | grep -B 5 -A 5 100232fb9
100232fa6:	e8 55 ea 07 00 	call	0x1002b1a00 ## symbol stub for: _CGGetLastMouseDelta
100232fab:	48 8b 45 98 	mov	rax, qword ptr [rbp - 104]
100232faf:	03 45 c8 	add	eax, dword ptr [rbp - 56]
100232fb2:	48 89 45 98 	mov	qword ptr [rbp - 104], rax
100232fb6:	8b 45 a4 	mov	eax, dword ptr [rbp - 92]
100232fb9:	03 45 84 	add	eax, dword ptr [rbp - 124]
100232fbc:	89 45 a4 	mov	dword ptr [rbp - 92], eax
100232fbf:	48 8b 5d 88 	mov	rbx, qword ptr [rbp - 120]
100232fc3:	80 7d c7 00 	cmp	byte ptr [rbp - 57], 0
100232fc7:	0f 84 b8 00 00 00 	je	0x100233085
100232fcd:	e9 6f 01 00 00 	jmp	0x100233141

The addition instruction that we'd like to disable is three bytes long.

Armed with the offset of the instruction within the file and the instruction's length, we can patch the executable, replacing that one three-byte add instruction with three one-byte nop instructions:

$ dd if=<(printf "\x90\x90\x90") of=DOSBox obs=1 seek=0x233fb9 conv=notrunc
0+1 records in
3+0 records out
3 bytes transferred in 0.000063 secs (47483 bytes/sec)

Did that change the right instruction? Disassembling the same address again will show:

$ objdump -macho -disassemble -x86-asm-syntax=intel DOSBox | grep -B 5 -A 5 100232fb9
100232fa6:	e8 55 ea 07 00 	call	0x1002b1a00 ## symbol stub for: _CGGetLastMouseDelta
100232fab:	48 8b 45 98 	mov	rax, qword ptr [rbp - 104]
100232faf:	03 45 c8 	add	eax, dword ptr [rbp - 56]
100232fb2:	48 89 45 98 	mov	qword ptr [rbp - 104], rax
100232fb6:	8b 45 a4 	mov	eax, dword ptr [rbp - 92]
100232fb9:	90 	nop
100232fba:	90 	nop
100232fbb:	90 	nop
100232fbc:	89 45 a4 	mov	dword ptr [rbp - 92], eax
100232fbf:	48 8b 5d 88 	mov	rbx, qword ptr [rbp - 120]
100232fc3:	80 7d c7 00 	cmp	byte ptr [rbp - 57], 0

That's exactly what we wanted. Let's fire it up:

$ ./DOSBox
DOSBox version 0.74-3-3
Copyright 2002-2019 DOSBox Team, published under GNU GPL.
---
Killed: 9

Oh.

This crash happened too quickly, though. It can't be because the newly-patched mouse-related code doesn't make sense: the program didn't even get as far as opening a window, let alone trying to deal with mouse coordinates. Besides, the disassembly looks just fine.

More likely is that the change has invalidated a signature on the executable file. Certainly, other people asking about that error message seem to be dealing with code signatures.

Let's try signing the updated executable:

codesign -s - --force DOSBox

-s - avoids requiring any certificate and instead does 'ad-hoc' signing. --force is needed only because the file already has a signature.

This time, it runs ... and the patch did exactly what it was supposed to do:


Limitations

Obviously an emulator that's been hacked this way won't be any use for playing something like a point-and-click adventure that does need both axes of mouse input. And it's silly to have to switch between emulator binaries just to change this one thing.

A different, neater possibility would be to patch the Doom executable rather than the emulator on which I'm running it. I'm less sure how I'd find the bit of Doom that deals with the mouse, though.

Conclusion

Overall this was probably simpler than building my own DOSBox would have been, and was definitely faster than waiting for a new release. It was also more fun and instructive than either of those.

I've been trying to work out why I found this so satisfying. I think part of it is about the way it relies on the interplay between levels of representation.

One of Hofstadter's main points in Gödel, Escher, Bach is that the meaning of something can't be completely intrinsic to it. Whenever we try to read anything at all, we must first have some expectations about what its structure will be.

Patching a program in this way relies on the fact that the same string of bytes in an executable file can be interpreted both as a program in machine code and as just that same string of bytes, so that changing the bytes in the file will change the meaning of the program. It's like gene editing but for computer programs.

It feels good to put that into practice from time to time.


Monday 1 August 2022

Possibly the Crudest Pet Microchip Scanner in the World

This small project was simple and also difficult. The difficulties were mostly because of various things being undocumented or unclear. Now that those things have been worked out, there's not much to it.

Motivation

The goal was to be able to identify a particular cat who has arrived in the neighbourhood recently. We have an idea who its original owner might be, so a tag number ought to be enough to check that hypothesis.

Background

In the US this would not be easy to achieve because pet microchipping is optional and there are so many standards to choose from, some of which feature proprietary encryption.

Here in the UK, things are more straightforward. Microchipping of dogs is obligatory and is legally required to conform to ISO standards 11784:1996 and 11785:1996 based around 134.2kHz RFID. While microchipping of cats is only set to become mandatory some time in 2022, it is commonly done and usually to the same ISO standards.

For about £30 I could have bought a complete scanner. But why would I do that when, for only half the price, I could buy just the reader chip and a small antenna, and make life difficult for myself?

Parts

  • Arduino Uno (rev 3)
  • 134.2kHz RFID reader and antenna
  • USB A/B cable
  • 9V power supply for Arduino
  • Breadboard jumper wires
  • Solder and heat-shrink
  • Small cardboard box
  • Rubber bands

Equipment

  • Voltmeter
  • Soldering iron
  • Laptop
  • Test cat
Test cat


Assembly

Connecting the antenna is the only physically difficult part of assembling this. I soldered the ends of it to the halves of a female-female jumper wire, so that I could plug it onto the reader board easily. The antenna wire is coated in a thin, almost invisible layer of insulation so my first attempt to do this failed. It was worth testing that I could pass a current between the ends of the jumper wires, before calling this step complete. I used some heat-shrink to secure the joins and cover up my crap soldering.

The ends of the antenna can then be connected to the separate pair of male pins on the reader board. In the row of six pins on the other side of the board, only three need connecting:
  1. Ground
  2. +5V
  3. -
  4. Serial transmit
  5. -
  6. -


The Arduino provides a +5V power supply pin and accompanying ground pins. It only has one UART and that's needed for communicating with the laptop, but there's a readily available software serial port package meaning that we can use GPIO pin 2 to receive from the reader's pin 4.

The separate 9V power supply might not be strictly necessary, but without it the whole system would be dependent on power from the USB cable. It was hard enough to persuade the reader to emit any data at all and the small amount of documentation I had suggested that a stable power supply might help with that.

Programming

I tried VS Code with PlatformIO. It was great: very easy to set up and use. The Arduino code is in the appendix.

Testing

Scanning the test cat should give a sequence of 13 bytes on the serial console, starting with AA and ending with BB.

Decoding the Chip Number

The 13 bytes of output have the hexadecimal form AA 0F 08 00 cc cc nn nn nn nn nn xx BB in which:
  • cc cc is a big-endian, two-byte country code or manufacturer number;
  • nn nn nn nn nn is a big-endian, five-byte animal ID number;
  • xx is a checksum that should be the exclusive-or of the ten preceding bytes (i.e. not including the leading AA).
PETtrac, the most obvious place to look up a UK pet microchip number, expects a 15-digit number but doesn't say much more about what format that might have. The animal ID field only gives 12 decimal digits; the shortfall is accounted for by prefixing it with the three-decimal-digit country code.

Pitfalls

At first I tried to connect the antenna to the reader simply by winding the antenna leads round the pins on the reader. I hadn't worked with enameled wire before and it doesn't look much different from bare copper wire, so I didn't realise that that wouldn't actually make any connection!

The reader board claims to operate at 9600 baud. In the absence of further detail, that would probably mean no parity bits and one stop bit, but there was no way to know that except by successfully reading data from it.

Neither the product listing for the card reader nor the chip packing on the reader itself had any kind of part number or manufacturer information on it, making it very difficult to search upstream for documentation. Eventually the query ["134.2khz" em4305 uart] led me to a listing on Alibaba with slightly clearer explanation. The part there appears to be supplied by Taidacent (or Shenzhen Taida Century Technology Company, for long) but it's not clear to me whether that's a manufacturer or just another retailer and in either case it didn't yield any proper documentation.

As noted in Amazon reviews, the antenna range is pitifully short. Also I wasn't entirely sure whether the cat's chip would be in the expected place between her shoulder blades, or how much the antenna orientation might matter. Luckily the test cat enjoys being scratched quite hard, so I could rub her all over with the device and eventually I did get a reading from between her shoulders after all.

The SoftwareSerial setup allows one to specify inverted logic. For some reason, this seemed to make it easier to obtain output bytes from the reader board, but the output bytes (though largely repeatable) seemed to be junk. Having not yet successfully prompted any other output at all, I spend a while trying to decode them before deciding that was probably a waste of time.

The ISO specifications talk about a country code, so I was expecting the first two bytes of the code to give 826 for Great Britain. In the case of our test cat, the value was 977 which isn't any country code at all! More digging revealed that at some point the scope of the field seems to have been widened to include manufacturer codes assigned by the International Committee for Animal Recording (ICAR). 977 would correspond to Avid.

The test cat likes to rub her face on the corners of cardboard boxes and to chew things like wires, which can make getting a reading more difficult.

Discussion

The reader chip has its own UART and talks a very common serial protocol at 9600 baud. All the Arduino is really doing is providing a 5V power supply and echoing whatever it receives on its USB port. So it ought to be possible to drop the Arduino and replace it with a USB-to-UART adapter such as this one, moving the decoding logic to a script running on the host laptop.

Appendix A: Code

#include <Arduino.h>
#include "SoftwareSerial.h"

auto rfid_serial = SoftwareSerial(
/*receivePin=*/2,
/*transmitPin=*/3,
/*inverseLogic=*/false
);

auto& usb_serial = Serial;

static union {
struct {
uint8_t start_marker;
uint8_t fixed_code[3];
uint8_t country_code[2];
uint8_t animal_id[5];
uint8_t checksum;
uint8_t end_marker;
} fields;
char buffer[sizeof(fields)];
};

static_assert(sizeof(fields) == 13);

int bytes_read; // Index of next free byte in buffer.
int total_bytes_read = 0;

void decode_and_print_buffer() {
uint16_t country_code = 0;
for (int i = 0; i < 2; ++i) {
country_code = (country_code << 8) + fields.country_code[i];
}
uint64_t animal_id = 0;
for (int i = 0; i < 5; ++i) {
animal_id = (animal_id << 8) + fields.animal_id[i];
}
char decimal_buffer[16];
snprintf(
decimal_buffer, 16, "%03u%06lu%06lu",
country_code,
// Printing 64-bit integers seems not to work at all, so instead
// we print the two halves of the animal ID separately.
static_cast<uint32_t>(animal_id / 1000000),
static_cast<uint32_t>(animal_id % 1000000)
);
usb_serial.print("15-digit identifier: ");
usb_serial.println(decimal_buffer);
}

void setup() {
pinMode(LED_BUILTIN, OUTPUT);
usb_serial.begin(9600);
usb_serial.print("Initialising ... ");

rfid_serial.begin(9600);
if (!rfid_serial.isListening()) {
usb_serial.println("Failed to initialise software serial port.");
return;
}

digitalWrite(LED_BUILTIN, LOW);
usb_serial.println("Ready.");
}

void loop() {
if (rfid_serial.overflow()) {
usb_serial.println("Overflow!");
}
while (rfid_serial.available()) {
int available_byte;
if ((available_byte = rfid_serial.read()) != -1) {
usb_serial.print(available_byte, HEX);
usb_serial.print(' ');
++total_bytes_read;

// Don't start filling the read buffer unless this byte is a start
// marker. That way, if we ever get into some sort of unaligned state
// then it will be possible to recover eventually.
if (bytes_read == 0 && available_byte != 0xAA) {
usb_serial.println("Ignoring non-start byte.");
continue;
}

buffer[bytes_read++] = static_cast<char>(available_byte);
if (bytes_read == sizeof(fields)) {
usb_serial.println();
decode_and_print_buffer();
memset(buffer, 0, sizeof(fields));
bytes_read = 0;
}
} else {
usb_serial.println("That's odd. An available byte could not be read.");
}
}

// Flash the LED till we've read something, to show that the loop is
// still running.
digitalWrite(LED_BUILTIN,
total_bytes_read > 0 || ((millis() / 500) % 2 == 0));
}

Friday 5 March 2021

A Different Kind of Cube

Among the things that came into my possession last Christmas (thanks, Tom and Kate and Annie and James!) was a collection of wooden puzzles that includes this thing:
The cubes have an elastic string running through them, so that adjacent cubes can be rotated with respect to each other around the axis connecting their centres, but are otherwise fixed.

The aim of the puzzle is to fold this string of cubes into a larger cube. This turns out to be difficult, so I decided to do the traditional thing (c.f. this work from 2012 and everything that arose from it) and write a Haskell program to solve it for me.

Without further ado:
   
import Data.Bits (setBit, testBit)
type Snake = String
snake :: Snake
snake = "bwb/w/b/wb/w/bw/b/w/bw/bw/b/w/b/wb/wb/wb/wb"

This represents the snake that we’re trying to fit into a cube. ‘b’ and ‘w’ represent black and white blocks, and ‘/’ represents a change of direction. The only thing that matters for the solution is the location of the direction changes in this list; the ‘b’ and ‘w’ just make it easier to check that the representation matches the real object.

type Cube = Int
emptyCube = 0

This represents the state of the cube into which we’re trying to fit the snake. For a backtracking search, all we really need to record here is which of the 3x3x3 cells in the cube have already been filled - so that we don’t produce solutions where the snake intersects itself. So we can use the first 27 bits of an Int.

Now we’re ready to define functions for working with the cube: one to mark a cell as filled and one to see whether it’s filled.

bitIndex :: Position -> Int
bitIndex (x, y, z) = x*9 + y*3 + z
fill :: Cube -> Position -> Cube
fill cube pos = setBit cube (bitIndex pos)
isFilledAt :: Cube -> Position -> Bool
isFilledAt cube pos = testBit cube (bitIndex pos)

Our algorithm will work its way through the snake and the cube simultaneously, marking cells in the cube as used as it goes. It will need to know where it is in the cube, and in what direction it’s working.

type Position = (Int, Int, Int)
data Axis = X | Y | Z deriving (Eq, Show)
data Sign = Positive | Negative deriving (Eq, Show)
data Direction = Direction Axis Sign

If the algorithm encounters a change-direction marker in the snake then it can go in any of the four directions that are perpendicular to the current direction.

nextDirections :: Direction -> [Direction]
nextDirections (Direction a _) = [Direction a' d |
                                  a' <- [X, Y, Z],
                                  a' /= a,
                                  d <- [Positive, Negative]]

Otherwise, it will just move to the next cell, in the direction in which it’s already heading.

nextPosition :: Position -> Direction -> Position
nextPosition (x, y, z) (Direction a d) =
  case a
  of X -> (x+delta, y, z)
     Y -> (x, y+delta, z)
     Z -> (x, y, z+delta)
  where delta = if d == Positive then 1 else -1

We’ll need to know when we’ve reached the edge of the space.

outOfBounds :: Position -> Bool
outOfBounds (x, y, z) = or [i < 0 || i > 2 | i <- [x, y, z]]

Now we have all the parts we need to put together a search algorithm. We’ll build up a list of the directions in which the segments of the snake need to lie; that’s enough information to follow and it’s easier to interpret than, say, a list of the cell coordinates.

type PartialSolution = [Direction]
type Solution = PartialSolution

Here’s the core of the algorithm: a function that consumes one character of the snake description, works out what possible next steps there are, and collects the solutions found by each of those steps. At each step, it needs to know:

  • which cells of the cube have already been filled;
  • what the last-filled position and direction are;
  • what part of the snake remains to be placed in the cube;
  • what solution has been accumulated so far.
solutions' :: Cube
          -> Position
          -> Direction
          -> Snake
          -> PartialSolution
          -> [Solution]

If there’s nothing of the snake left to place, then we’ve found one solution. This is the base case for the recursive search.

solutions' _ _ dir [] soFar = [dir:soFar]

If the next character in the snake indicates a change of direction, then we have to look for solutions in each of the four possible next directions. We don’t change position or mark any cell as filled, but we’ll need to include the change of direction in any solutions we find.

solutions' cube prevPos dir ('/':snake') soFar =
  concat [solutions' cube prevPos dir' snake' (dir:soFar) |
          dir' <- nextDirections dir]

Otherwise, we must mark our position as filled - or return empty-handed if that’s impossible - and move to the next cell, in the same direction as before.

solutions' cube prevPos dir (c:snake') soFar =
  let pos = nextPosition prevPos dir in
  if outOfBounds pos || cube `isFilledAt` pos then []
  else solutions' (fill cube pos) pos dir snake' soFar

A small wrapper describes the starting conditions for the search.

solutions snake = map reverse $ solutions' emptyCube
                                           (-1, 0, 0)
                                           (Direction X Positive)
                                           snake
                                           []

Finally, we will show the directions in a solution as crude arrows.

instance Show Direction where
  show (Direction X Positive) = ">"
  show (Direction X Negative) = "<"
  show (Direction Y Positive) = "^"
  show (Direction Y Negative) = "v"
  show (Direction Z Positive) = "."
  show (Direction Z Negative) = "o"
main = mapM_ putStrLn $ map (unwords . map show) $ solutions snake

We get just two solutions:

> ^ < . ^ o > . v < ^ o ^ . v > ^
  > . < ^ . v > ^ o < . v . ^ o > .

which are actually just the unique solution and its mirror image.



Wednesday 15 January 2020

Instructions for Replacing the Thermostat of a TKR6620/2 Fridge/Freezer


The new thermostat cost me £22 including delivery, whereas a comparable new fridge would be about £350. This was a fiddly job, though.
  1. Turn the fridge off at the mains.
  2. Squeeze the top and bottom of the light cover to release it. Unscrew the light bulb and put it in a safe place.
  3. Behind the light bulb is a clip. Open this with a screwdriver and then unclip the entire thermostat/light assembly from the wall of the fridge by pulling it forwards.
  4. Behind the thermostat/light assembly is a panel, beneath which is a connector block for the power supply. Disconnect this.
  5. Pull the thermostat/light assembly forwards, bringing the thermostat phial out with it. Note where the phial comes out of the thermostat assembly and where it enters the fridge wall!
  6. Remove the temperature control knob from the thermostat assembly with a screwdriver.
  7. Remove the spring clip from around the shaft of the thermostat control underneath the knob.
  8. Remove the cover of the assembly by unclipping it at the back and sliding it off forwards.
  9. Unplug the thermostat and remove it from its connector block.
  10. If the new thermostat has a different pin layout from the old one, then:
    1. Remove the thermostat power supply spade connectors from the connector block. Each has a small spring clip on the underside, which can be opened by jamming a very small flat-blade screwdriver into the connector from the thermostat end.
    2. Use the tiny numbers next to the thermostat power supply pins on the old and new thermostats to work out which power connectors need to go where.
    3. Push the power supply spade connectors into the connector block in the new configuration. Some connector blocks are made to accommodate different pin layouts from opposite ends, so this might require turning the connector block round.
  11. Attach the new thermostat to the power supply connector block and seat everything back in the thermostat/light assembly.
  12. Put the cover back on the thermostat/light assembly. The new thermostat phial and the power connector should poke out, ready to fit back into the fridge wall.
  13. Refit the spring clip on the shaft of the thermostat control.
  14. Put the temperature control knob back on.
  15. Uncoil the phial and feed it back into the fridge wall.
  16. Once the thermostat/light assembly is close enough, attach the power supply and clip the assembly back onto the wall.
  17. Put the light bulb back in and replace its cover.
  18. Turn the fridge back on.


Thursday 12 December 2019

Once Upon a Time There was a Bee

Some results from talktotransformer.com, all starting with the same prompt: Once upon a time there was a bee.

A Murderous One

Once upon a time there was a bee. A beautiful bee with a beautiful crown of flowers. It was such a bee that you couldn't kill it because it was a beautiful bee. It had such a pleasant fragrance and the most wonderful sound. But one day, it started to sting. You couldn't do anything because you were so stung. It was terrible to see it, you could hardly take it in. You didn't know what to do, but you tried to kill it and you failed. Finally, when it became impossible to control the sting, the bee was finally killed. So now there's a bee that you don't kill because it's a beautiful bee, and it's so lovely that you have to kill it. Then the bee is so horrible that you can't kill it, and so you have to kill it. You have to kill it. You have to kill it. That's what this whole book is about. You have to kill it. You have to kill it

A Transformational One

Once upon a time there was a bee. Then the bee became a wasp, and the wasp became a spider, and the spider became a scorpion, and the scorpion became a centipede, and the centipede became an ant, and the ant became a human being. And it happened, that in the midst of that great mass of diversity, each individual insect became a member of this vast family."

If these "bastions of humanity" are to be believed, then it's important to note that the United States was founded as a British colony and that every U.S. President since its inception has been a British subject.

However, since the United States is the only country on earth that still has a standing army (and we know what happens when an army is abandoned by its commander), one might wonder why it would choose to "liberate" itself from an enemy of the British crown by establishing a foreign colony.

A Disturbed One

Once upon a time there was a bee. It was a beautiful bee with a beautiful name. It didn't sting anybody, it didn't sting me or my kids or anybody else. It was just a lovely bee.

The bee was named Vanessa. And it was beautiful.

And then one day the bee died.

The next day the bee died.

Vanessa died.

The bee died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

The bee died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

Vanessa died.

A Scientific One

Once upon a time there was a bee. It was pretty small, a few millimeters across, but there was a big beehive on its back. It had a huge, thick, black combs. When you stepped inside, the bees in the combs would fly away and then you'd have to pick them up. They were huge. You could get so much honey by holding the comb in front of you and letting the bees work. The honey was like honey from a honey pot. It was so sweet and delicious. It was a special kind of sweetness, which was the main reason I became a beekeeper.

What did you do after your beekeeping job?

I decided I would be a scientist. I wanted to be a chemist because I thought it was much more interesting than beekeeping. I took some chemistry courses at the University of Texas. I took a physics course and a microbiology course, but I didn't finish them. But when I finished them,

A Fairy-Tale One

Once upon a time there was a bee. And that bee was very good to his queen. And the bees said: "We can't have him anymore." And they went into the garden and threw him out.

A woman was sitting in a garden. She was very beautiful to look at. She was humming a beautiful melody. The bee flew by her and landed in her lap.

And the woman said: "Come back to me, honey."

And the bee said: "I can't come back to you again. I'm a busy bee."

Then the woman said to the bee: "If you want to have the bee back, just promise to come back to me. If you do, I'll give you something nice to eat."

And the bee said: "If that's what you want, I'll be back."

But the woman thought to herself: "It's a promise that I can't make."

And so

Tuesday 29 October 2019

Fiddling with Old WiFi and DSL Hardware

The Problem

Some time ago, AAISP supplied me with an EchoLife HG612 broadband modem and a separate Technicolor TG852n WiFi hub, to be connected to each other by Ethernet. Since the phone socket is at the back of the house but I wanted the WiFi signal to be strongest at the front of the house, I put them in separate rooms and connected them with Ethernet-over-power devices. All was well till I also needed connectivity at the back of the house, where the WiFi signal is almost non-existent.

I found that a device with a wired link directly to the modem did not work: it could not talk to devices connected to the WiFi. The WiFi hub was configured to serve DHCP, while the modem was not. As far as I can tell, the problem was that the WiFi hub would not serve DHCP to a device connected via the modem.

The Solution

It's possible to set up the broadband modem to act as a DHCP server, and then turn off the WiFi hub's DHCP router. That way, every device on the network – whether wired to the WiFi hub, wired to the modem, or wireless – can get an IP address from the modem and end up on the same subnet, so they can all talk to each other. But neither step is straightforward.

Set up DHCP on the Modem

Enable the Web UI

(These steps are derived from AAISP's instructions.)

  1. Download a version of the HG612 firmware whose Web UI has been enabled. A suitable version is called bcm96368MVWG_fs_kernel_HG612V100R001C01B030SP08_webgui and is available in this repository. It has an MD5 hash of 5d6cb9987c52f4e687c624a614adaa29.
  2. Factory-reset the modem and use that file to flash its firmware, by following the instructions here.

Set up the Broadband Connection

  1. Set up wired connection from a computer to the LAN2 socket on the HG612.
  2. Give the computer an IP address of 192.168.1.100.
  3. Log in to the Web UI (192.168.1.1) with username/password admin/admin
  4. Under Basic->WAN->ptm1.101:
    • WAN connection: Enable
    • Service list: INTERNET
    • Port binding: Disable both LAN1 and LAN2
    • Connection mode: Route
    • Connection Type: PPPoE
    • User name/Password: as provided by the ISP

Set up the LAN


  1. Under Basic->LAN:
    • IP address: 192.168.1.1
    • Subnet mask: 255.255.255.0
    • DHCP Server: Enable
    • Start IP address: 192.168.1.2
    • End IP address: 192.168.1.250
    • Primary DNS server address: 4.4.4.4
    • Secondary DNS server address: 8.8.8.8

Disabling DHCP on the WiFi hub


  1. Work out what the IP address and admin username/password of the TG852n will be after the not-quite-factory reset. These vary according to the supplier, because it is set by a file called isp.def that is not removed by the use of the reset button.
    • For AAISP they are 192.168.1.254 and Administrator/admin.
  2. "Factory"-reset the TG852n by pushing a pin into the reset hole till the power light turns orange.
  3. Set up a direct wired connection from computer to the TG852n (any port other than 4).
  4. Log in to the Web UI (Only works reliably via wired connection to port (other than 4). May require multiple login attempts.)
  5. Under Home Network->Interfaces->LocalNetwork:
    1. Configure (top right)
      • Use DHCPv4 Server: No
      • Use Stateless DHCPv6 Server: No

Bonus Points: Adding an extra WiFi hub

I acquired an additional TG852n as a result of Demon Internet shutting down. Since I already had one of these, I thought it would be simple to add another, to provide a strong WiFi signal at the back of the house. I was wrong.

For Demon Internet the WiFi hub's default IP address and admin username/password are given as 192.168.254.254 and admin/DemonCPE.

No amount of resetting and fiddling could convince the device that its IP address should be 192.168.1.x and not 192.168.254.x. Eventually I guessed that I would need to remove the device's isp.def file. To do that I would need to use FTP. I couldn't find an FTP server on its port 21, so I thought I would telnet into it instead and see what I could find. Of course, OSX no longer comes with a telnet client, so first I had to download inetutils and build my own. Luckily that did work straight away. With the help of a TG852n CLI Guide still available from Demon, I tried a service system list and found that the FTP server was running, but on port 2121. Finally I could FTP to the hub, delete the offending isp.def and get back to a known state.

From that state it was simple to use the Web UI to set the TG852n's IP address to 192.168.1.253 and turn off its DHCP server. Then I set up the WiFi to match the existing hub's details (though on a different channel). When I plugged the additional hub into the same switch, it worked!

Finishing up

While I was checking that the two WiFi hubs could each see any devices that were connected to either of them, I noticed some extra IP addresses assigned to their MAC addresses. It turns out that, as shipped, these hubs are set up to act as media servers over UPnP and Windows Networking. I took the opportunity to turn those off for now. Finally, I changed the admin login details so that they all matched.