Robico Engine

From IFWiki

The below sections desribe some of the work that has been performed trying to decode the Robico Engine. This work mainly describes Rick Hanson, but the general data format seems to apply to the rest of the games in the Saga of a Spy trilogy.

The Files

There are 6 files within Rick Hanson:

  • rhanson - a BBC BASIC loader program
  • RICK - a 6502 machine code loader program
  • RICK1 - mainly display code
  • RICK2 - unknown
  • RICK3 - the dictionary of compressed words
  • RICK4 - the main game file (including code)

Word Compression

All messages (descriptions, responses etc.) have been compressed using a basic block compression algorithm. The file RICK3 contains the dictionary of compressed words. RICK4 contains (amongst other things) a byte look up to the dictionary entries to decompress the text.

Format of Files

RICK3

The dictionary entries are in sequential order starting from position 0. All phrases are in upper case, separated by the @ character. A CR (hex 0x0d) character signifies the end of the list.

For example: ENTRANCE @MOUNTAIN@CORRIDOR@

RICK4

This file contains most of the components of the game, including the machine code for the interpreter. The below table references the major sections of the file:

0x0    - 0x0ff  = Room exits for North, South, East and West and the count of exits.
0x100  - 0x1ff  = Room exits for Up, Down, In, Out, North-east, North-west, South-East and South-West.
0x200  - 0x2ff  = Room descriptions.
0x300  - 0x3e4  = Extra room descriptions
0x3e5           = Number of objects
0x3e6  - 0x40f  = ??
0x410  - 0x42d  = Flags (seem to state whether an object has more than one description)
0x42e  - 0x44b  = Initial object locations
0x44c  - 0x469  = Object names, followed by the initial description of the objects. Add 0xdc to the number to get the message number.
0xbf3  - 0x1c9d = Machine code
0x1c9e - 0x1f3b = Vocabulary
0x1f3c - 0x21bf = Room exits (i.e. the rooms that the exit goes to) In a direct list for each room.
0x21c0 +        = Messages

Room encoding

To be able to recreate the full description for the room, several areas need to be referenced:

  1. The room description needs to be pulled from its offset in the (0x200 - 0x2ff) section. This is prefaced by either 'You're' or 'You are'.
  2. If the appropiate byte is non-zero in the (0x300 - 0x3e4) section then that message is added as an extra sentence.
  3. Exits for the room are flagged from the room offset in both the (0x0 - 0x0ff) and (0x100 - 0x1ff) sections, these are endoded as:
0x0   - 0x0ff: WESNCCCC
0x100 - 0x1ff: OIDUABCD
Where:
W = West
E = East
S = South
N = North
C = Count of exits
O = Out
I = In
D = Down
U = Up
A = North west
B = North east
C = South west
D = North east
  1. The room which the set exits go to are read from the (0x1f3c - 0x21bf) section. These are read directly in room order and have one room number for each exit flagged.
  2. Objects are given a name specified at the (0x44c - 0x469 section), this contains the name of the object, followed by its examine description. Also here are extra descriptions which may be flagged by the (0x410 - 0x42d) section. The starting locations for the objects are stored at (0x42e - 0x44b)
  3. The vocabulary (both nouns and verbs) are trivially obscured in the (0x1c9e - 0x1f3b) section/

Sample Code

Below is sample code used to extract most of the Rick Hanson information. This code has been hacked together, so it may contain programming errors and inefficencies.

getdict.c

The following extracts the list of messages:

#include <stdio.h>

int main (void)
{
   FILE *infile;
   char array[255][30], temp[30];
   unsigned char byte, byte2;
   int i=0,size=0,ptr=0,lower=0,bracket;
   int count=0;

   infile=fopen("$.RICK3","rb");

   do
   {
      byte=fgetc(infile);
      if ( byte != 0x40 && !feof(infile) )
      {
         array[i][ptr++]=byte;
      }
      else if (!feof(infile))
      {
       array[i][ptr++]='\0';
       ptr=0;
         ++i;
      }
   } while (!feof(infile));
   size=i;

   fclose(infile);

   /* Now open the main file */
   infile=fopen("$.RICK4","rb");

   fseek(infile,0x21c0,SEEK_SET);
   //printf("%4x %2x: ", ftell(infile),count);

   do
   {
      byte=fgetc(infile);
      if (byte < size)
      {
         /* look up in dictionary */
         if (lower==1)
         {
            printf("%c",array[byte][0]);
         }
         for (i=lower;i<strlen(array[byte]);i++)
         {
            printf("%c",tolower(array[byte][i]));
         }
         lower=0;
      }
      else
      {
        /* look it up */
        if (byte == size)
        {
            count++;
            printf("\n");
            //printf("\n%4x %2x: ", ftell(infile),count);
        }
        else if ((byte - size) < 27)
        {
          byte2=(byte-size);
          byte2+=(lower==1)?64:96;
          printf("%c",byte2);
          lower=0;
        }
        else
        {
         switch (byte-size)
         {
            case 36: printf("\""); break;
            case 35:
               if (bracket == 0) { printf("("); bracket=1; }
               else { printf(")"); bracket=0; } break;
            case 34: lower=1; break;
            case 33: printf("'"); break;
            case 32: printf(" "); break;
            case 31: printf(": "); break;
            case 30: printf(", "); break;
            case 29: printf("! "); lower=1; break;
            case 28: printf("? "); lower=1; break;
            case 27: printf(". "); lower=1; break;
            default: printf("!!!!!!%x!!!!!!!",(byte-size)); break;
          }
        }
      }
   } while (!feof(infile));

   fclose(infile);
   return 0;
}

getrooms.c

The following extracts rooms, objects and vocabulary:

#include <stdio.h>

char words[512][512];
typedef struct
{
   unsigned char n,s,e,w;
   unsigned char u,d,i,o;
   unsigned char se,sw,ne,nw;
} roomexits;

int main(void)
{
   FILE *messages;
   FILE *infile;
   FILE *exitsptr, *exitsptr2;
   FILE *roomptr;
   char strings[512][512];
   char descriptions[512];
   char extradesc[512];
   roomexits exits[2048];
   int numexits[512];
   int objects[512];
   int objectlocs[512];
   //need to malloc
   //char words;
   int numobjects, numwords;
   int i=0, j=0, count=0, loc=0, k=0, ptr=0;
   unsigned char blank=255;


   messages=fopen("messages.txt","r");
   if (messages == NULL)
   {
      fprintf(stderr,"Could not open messages file\n");
      exit(1);
   }
   infile=fopen("$.RICK4","rb");
   if (infile == NULL)
   {
      fprintf(stderr,"Could not open data file\n");
      exit(1);
   }
   // Open exitsptr so we can look at the same file twice!
   exitsptr=fopen("$.RICK4","rb");
   if (exitsptr == NULL)
   {
      fprintf(stderr,"Could not open data file\n");
      exit(1);
   }
   exitsptr2=fopen("$.RICK4","rb");
   if (exitsptr2 == NULL)
   {
      fprintf(stderr,"Could not open data file\n");
      exit(1);
   }

   while (!feof(messages))
   {
      fscanf(messages,"%[^\n]",strings[i]);
      fgetc(messages);
      i++;
   }
   fclose(messages);

   fseek(infile,0x200,SEEK_SET);
   for (i=0; i<256; i++)
   {
      descriptions[i]=fgetc(infile);
   }
   for (i=0; i<255;i++)
   {
      exits[i].n=blank; exits[i].s=blank; exits[i].e=blank; exits[i].w=blank;
      exits[i].u=blank; exits[i].d=blank; exits[i].i=blank; exits[i].o=blank;
      exits[i].se=blank; exits[i].sw=blank; exits[i].ne=blank; exits[i].nw=blank;
   }

   // Check extra descriptions
   fseek(infile,0x300,SEEK_SET);
   for (i=0; i<256; i++)
   {
      j=fgetc(infile);
      if (j != 0) extradesc[i]=j+descriptions[i];
   }
   // Now to work out the exits - first the easy ones: news
   fseek(infile,0x0,SEEK_SET);
   fseek(exitsptr,0x1f3c,SEEK_SET);
   fseek(exitsptr2,0x100,SEEK_SET);
   for (i=0; i<256; i++)
   {
      j=fgetc(infile);
      // Get number of exits
      numexits[i]=j & 0xf;
      k=j & 0xf;

      // Get exits
      if ((j & 0x10) != 0) exits[i].n=fgetc(exitsptr);
      if ((j & 0x20) != 0) exits[i].s=fgetc(exitsptr);
      if ((j & 0x40) != 0) exits[i].e=fgetc(exitsptr);
      if ((j & 0x80) != 0) exits[i].w=fgetc(exitsptr);

      // Get other exits
      j=fgetc(exitsptr2);
      if ((j & 0x10) != 0) exits[i].u=fgetc(exitsptr);
      if ((j & 0x20) != 0) exits[i].d=fgetc(exitsptr);
      if ((j & 0x40) != 0) exits[i].i=fgetc(exitsptr);
      if ((j & 0x80) != 0) exits[i].o=fgetc(exitsptr);
      if ((j & 0x01) != 0) exits[i].se=fgetc(exitsptr);
      if ((j & 0x02) != 0) exits[i].sw=fgetc(exitsptr);
      if ((j & 0x04) != 0) exits[i].ne=fgetc(exitsptr);
      if ((j & 0x08) != 0) exits[i].nw=fgetc(exitsptr);
   }
   fclose(exitsptr);
   fclose(exitsptr2);

   // Get objects
   // Get number of objects
   fseek(infile,0x3e5,SEEK_SET);
   numobjects=fgetc(infile)+1;
   fseek(infile,0x44c,SEEK_SET);
   for (i=0;i<numobjects;i++)
   {
      j=fgetc(infile);
      objects[i]=j+0xdc;
   }
   fseek(infile,0x42e,SEEK_SET);
   for (i=0;i<numobjects;i++)
   {
      j=fgetc(infile);
      objectlocs[i]=j;
   }

   // Now get words
   fseek(infile,0x1c9e,SEEK_SET);
   i=0; ptr=0;
   do
   {
      j=fgetc(infile);
      // Check for high bit set
      if ((j & 0x80) == 0x80)
      { // end of word
         words[i][ptr++]=j & 0x7f;
         i++;
         ptr=0;
      }
      else if (j != 0xff)
      {
         if (j > 0x50 )
         { // It's a number
            j-=0x50;
            words[i][ptr++]=(((j / 4)+1)*4) - ((j % 4) + 1) + 48;
         }
         else
         { // It's a letter
            words[i][ptr++]=(((j / 4)+1)*4) - ((j % 4) + 1) + 96;
         }
      }
   } while (j != 0xff && i < 255);
   numwords=i-1;

   // Print descriptions
   for (i=0; i<250; i++)
   {
      printf("Room %d\n",i);
      printf("Description text:\n");
      j=1;
      printf("%s%s", strings[j], strings[descriptions[i]]);
      if (extradesc[i] != 0) printf("%s",strings[extradesc[i]]);
      printf(".\n");
      printf("Exits: %d\n",numexits[i]);
      if (exits[i].n < 255) printf("North to %d (%s)\n",exits[i].n,strings[descriptions[exits[i].n]]);
      if (exits[i].s < 255) printf("South to %d (%s)\n",exits[i].s,strings[descriptions[exits[i].s]]);
      if (exits[i].e < 255) printf("East to %d (%s)\n",exits[i].e,strings[descriptions[exits[i].e]]);
      if (exits[i].w < 255) printf("West to %d (%s)\n",exits[i].w,strings[descriptions[exits[i].w]]);
      if (exits[i].i < 255) printf("In to %d (%s)\n",exits[i].i,strings[descriptions[exits[i].i]]);
      if (exits[i].o < 255) printf("Out to %d (%s)\n",exits[i].o,strings[descriptions[exits[i].o]]);
      if (exits[i].u < 255) printf("Up to %d (%s)\n",exits[i].u,strings[descriptions[exits[i].u]]);
      if (exits[i].d < 255) printf("Down to %d (%s)\n",exits[i].d,strings[descriptions[exits[i].d]]);
      if (exits[i].se < 255) printf("Southeast to %d (%s)\n",exits[i].se,strings[descriptions[exits[i].se]]);
      if (exits[i].sw < 255) printf("Southwest to %d (%s)\n",exits[i].sw,strings[descriptions[exits[i].sw]]);
      if (exits[i].ne < 255) printf("Northeast to %d (%s)\n",exits[i].ne,strings[descriptions[exits[i].ne]]);
      if (exits[i].nw < 255) printf("Northwest to %d (%s)\n",exits[i].nw,strings[descriptions[exits[i].nw]]);
      printf("\n");
   }

   for (i=0;i<numobjects; i++)
   {
      printf("Object %d: %s\n",i,strings[objects[i]]);
      printf("Description: %s\n",strings[objects[i]+1]);
      printf("Starts in %d (%s)\n",objectlocs[i],strings[descriptions[objectlocs[i]]]);
      printf("\n");
   }

   for (i=0;i<numwords;i++)
   {
      printf("Word %d: %s\n",i,words[i]);
   }

   fclose(infile);
   return 0;
}