Wednesday, 22 October 2014

Port of Last Eichhof game to Allegro library

There was a game called "Last Eichhof" released in 1993 for MS-DOS. The source code was released by the original coder (Danny Schoch) - I don't know exactly when - and is available from various places on the Internet.

Earlier this year I ported this code to the Allegro games programming library (http://alleg.sourceforge.net/). The source is available from here. The ported version smooths the motion of sprites on the screen and makes the controls more responsive (so the minimum amount you can move your ship with a tap of a key is smaller).

There were several points of interest. The assembly routines had to be rewritten completely, of course, which included the main game loop and the graphics routines. I had to learn about the parts of the VGA programming interface that the original used - but I feel I only scratched the surface of it, as it is quite horribly complicated. I managed to avoid having to read much about Sound Blaster programming - the only complication was how to decode the sound samples in the data files, which were in something called 8-to-4 bit ADPCM. Fortunately I found an algorithm on a website for decoding it.

Another unusual feature of the source was the format of its data files, and how they were read. There are lines like this in the original source code (from GAMEPLAY.C):

// Load weapon sprite library.
   ptr = loadfile(datapool, "weapons.sli");
   weapon.nsprites = *ptr;
   weapon.sprite = (struct sTableEntry *)(ptr+1);
   for (i = 0; i < weapon.nsprites; i++) {
      (long)weapon.sprite[i].sprite += (long)weapon.sprite;
   }

The call to 'loadfile' gives a pointer to the data. This starts off as a number of sprites, followed by offsets into the data, followed by the data itself. 'weapon.sprite' is set to point directly into this data area. This is an array of 'struct sTableEntry', with the exception that the 'sprite' fields of each 'struct sTableEntry' are a kind of offset, and not pointers. The loop converts these offsets into true pointers (of type 'struct sprstrc * far'), each a pointer to a structure with information about the sprite in it.

There are a few tricky points here. The first is that the compiler has to give 'struct sTableEntry' exactly the right layout for this to work, with the right padding, widths and endianness. This kind of "deserialization" is not portable between compilers or architectures.

Converting the "long" fields into far pointers relies on a "long" type being the same width as a far pointer. (Note C programming for MS-DOS had two kinds of pointer, called "near" and "far". The "far" pointer was 32 bits and could access a wider address space than the 16-bit near pointer.) Note that the "offsets" are not simple numbers giving the number of bytes into the data area: they are a very strange type of value, being the numeric difference between two far pointers considered as numbers. This would appear very dodgy - whether we get a pointer to the right place could depend on the address of 'weapon.sprite'.

These lines took me some time to understand what is going on, considering that a cast on the left-hand side of an assignment statement is not supported by modern C compilers. I believe it means:

weapon.sprite[i].sprite = (struct sprstrc * far) ((long) weapon.sprite + (long) weapon.sprite[i].sprite);
When I was playing this game under MS-DOS, it took me a long time to beat, and I only beat it once. I have beaten the original once or twice in DosBox, after practising on the Allegro version. I think the Allegro version is slightly easier because of the motion smoothing making it easier to see where enemies are moving on the screen, as well as the more responsive controls.

No comments:

Post a Comment