The random ramblings of a French programmer living in Norway...
2021
← Pitching RidersAdeline's LibMenu →
  Starting at Adeline Software
Fri 14th May 2021   
Adeline Software
Part 1 Part 2  

Adeline Software was my first video game employer: I guess I could have started in a much worse place! Obviously, over time we tend to skip over the crapy times1 and only remember the rosy memories, but what's sure is that working on Time Commando and Little Big Adventure has taught me a lot.

Quick Start

I don't remember what I was expecting when I joined, but what's sure is that it was not to just be given a computer with full access to everything, a pile of documentation, and a "go ahead, play with the libs, do whatever you want". In January 1995 we were still using MSDOS as the main development platform for the entire team2, but we knew that Windows 95 was coming so there was a transition period where some of the developers were testing and updating internal systems so we could migrate to Windows. When I arrived, my main machine was a 486 DX 50 machine3 with a few megabytes of RAM, a CD ROM Drive4 and a 15 inch VGA monitor5.
Novel NetWare Server Installation
Novel NetWare Server Installation
The entire team had access to a main server on which all the source code and libraries were accessed from a mapped drive, so in the configuration files and source code you will often see references to the mystical F: drive.

Network structure

When I arrived, Adeline was still using Novel NetWare with a 10BASE2/BNC based network where basically every computer is connected to every other one. If for some reason6 one of the node failed, the entire network would fail, making diagnostic a long and painful operation7.
10BASE2 network using BNC connectors
10BASE2 network using BNC connectors
Soon after the system was upgraded to use a Windows NT server, but most of the team was still using DOS clients, so the NT server was basically just there as a file server mapped on the F: letter. The network storage was organized in the following way:
  • PROJECT, with subfolders for each project (so LBA, TIME)
  • TOOLS, for the published tools executables (ANIMIT, TCED, ACF tools, ...)
  • COMPIL, where you would find the Watcom compiler, TASM, etc...
  • PANIER, for personal storage and data exchange
TRIVIA: The following BAT file running on one of the DOS client machines will make the NT server use 100% of the CPU and basically stop serving files:
:: NT Server Killer 1.0 (c) "Oop, sorry, did not know!" :Loop IF EXIST F:\SOMEFILE\ON.THE\NETWORK\TESTFILE.TXT GOTO FoundIt GOTO Loop :FoundIt
(And yes, if you were wondering: Just adding a tiny pause before the GOTO drops the CPU usage to 0%)

The Adeline libraries

I had never used libs before, so I had to ask around about how to even start, and Sébastien Viannay pointed me to a small program which had the basic initializations and displayed a text on the screen.
Adeline Software developer documentation
Adeline Software developer documentation
I decided to start by converting my university "ZBuffer house" project, originally designed to compile using Borland C++ in VGA 13h mode8 to the Watcom C with the Adeline libraries. The main difference between the two environments is that Borland C++ was used in normal segmented mode, so to access large blocks of memory you had to use FAR pointers, which internally meant that the compiler generated very inefficient code involving changing segments for pretty much every single memory access, resulting in a very slow code. The Watcom compiler on the other hand was designed to use a dos extender (DOS4GW.EXE) which gave us some real 32 bit pointers so we could access the memory linearly without too many performance issues. The end result is that the two demos are running pretty much at the same speed... except one is rendering 4.8 times as many pixels since it runs in SVGA9 resolution. You can see the demo here (first the Adeline converted version, then the original executable made at the university) captured from DOSBOX.
From there I quickly figured out how to load a PCX10 image, read the keyboard and mouse, and basically a couple days later I had a minimalistic painting program running.

MiniDraw 1.0

The word "minimalist" was not just used randomly: Basically all this program does is to allow you to load or save a picture (specifically 640x480 in 256 colors PCX files), zoom and scroll it, and select a color to freehand draw in full screen mode. That's really all there is, and you can see that on this video.
The reason I'm showing that, is mostly because I do have the source code for it, in all glorious 300 lines of code... which I can probably share without any lawyer knocking on my door. So, if you were wondering what the actual source code of an Adeline software program looked like (in term of code structure, coding conventions, libraries calls, etc... ) here is the complete source code of the small "painting" program I wrote to train myself when I arrived at Adeline. The date is January 5 1995, so basically it took me about a week to get it up and running. The only changes I did are some reformatting on longs lines so it fits on the blog, and some accentuated characters fixed from CODEPAGE 437 to UTF-8, but otherwise it's unmodified.
//
//
// TEST.C
//
// 5 janvier 95
//
// Juste pour tester les libs de menu, et le watcom C
//
//
#include <stdio.h>

#include "\projet\lib386\lib_sys\adeline.h"
#include "\projet\lib386\lib_sys\lib_sys.h"
#include "\projet\lib386\lib_svga\lib_svga.h"
#include "\projet\lib386\lib_menu\lib_menu.h"
#include "\projet\lib386\lib_3d\lib_3d.h"

UBYTE   PcxPathname[255];

UBYTE   *Image;
UBYTE   pal[768];

UBYTE   *Screen1;
UBYTE   *Screen2;

T_MENU MenuTest;

WORD couleur;

#define flag_plein 1

WORD interieur;

#define id_crayon 1
#define id_ligne  2
#define id_cadre  3
#define id_cercle 4

#define id_charge 5
#define id_sauve  6

#define id_plein  7

#define id_couleur 8

#define id_quitte 9

#define id_fenetre 10

#define id_xpos 15
#define id_ypos 16

#define id_zoom 17
#define id_spy 18

WORD ox,oy;
WORD zoom;

LONG xs1,xs2,ys1,ys2;
LONG xfen1,xfen2,yfen1,yfen2;
LONG largeur_fenetre;
LONG hauteur_fenetre;

WORD spy_var;


void ChangeChangeValue(T_MENU *ptrmenu, WORD handle, WORD step, WORD minvar, 
                        WORD maxvar, WORD flagaff)
{
    WORD n, nblcb,value;
    T_CLICK_BOX *ptrlcb ;

    nblcb = ptrmenu->NbBox ;            // Nombre de boites...
    ptrlcb = ptrmenu->PtrMallocList ;   // Début de la liste...

    for (n=0;n<nblcb;n++)
    {
        if ((ptrlcb->Handle==handle) AND (ptrlcb->Type==TYPE_CHANGE_VALUE))
        {
             value=*(ptrlcb->PtrVar);
             if (value<minvar) value=minvar;
             if (value>maxvar) value=maxvar;
             *(ptrlcb->PtrVar)=value;
             ptrlcb->Mask=step;
             (ptrlcb+1)->Mask=minvar;
             (ptrlcb+2)->Mask=maxvar;
             if (flagaff) DrawBox(ptrmenu,n,NO_FLAG,TRUE);
             return;
        }
        ptrlcb++ ;
    }
}


void InitMenus()
{
  InitPalMenu();
  OpenMenu(&MenuTest,42,31);
  AddText(&MenuTest,0,0,7,1,FLAG_CONTOUR,"MiniDraw 1.0");

  AddButton(&MenuTest,id_crayon,1,3,7,2,FLAG_CENTRE,"Crayon");
  AddButton(&MenuTest,id_ligne,1,5,7,2,FLAG_CENTRE,"Ligne");
  AddButton(&MenuTest,id_cadre,1,7,7,2,FLAG_CENTRE,"Cadre");
  AddButton(&MenuTest,id_cercle,1,9,7,2,FLAG_CENTRE,"Cercle");

  AddButton(&MenuTest,id_charge,1,12,7,2,FLAG_CENTRE,"Charge");
  AddButton(&MenuTest,id_sauve,1,14,7,2,FLAG_CENTRE,"Sauve");

  AddSwitch(&MenuTest,id_plein,1,17,4,1,FLAG_CENTRE,"PLEIN",&interieur,flag_plein);

  AddChangeValue(&MenuTest,id_couleur,1,19,7,1,NO_FLAG,"COL:",&couleur,1,0,255);

  AddButton(&MenuTest,id_quitte,1,28,7,2,FLAG_CENTRE+FLAG_RED,"QUITTER");

  AddWindow(&MenuTest,id_fenetre,9,0,33,31,FLAG_PUSHED);
  GetCoorButton(&MenuTest,id_fenetre,&xfen1,&yfen1,&xfen2,&yfen2);
  largeur_fenetre=xfen2-xfen1;
  hauteur_fenetre=yfen2-yfen1;
  AddChangeValue(&MenuTest,id_xpos,1,25,7,1,NO_FLAG,"X:",&ox,1,-5,639-largeur_fenetre);
  AddChangeValue(&MenuTest,id_ypos,1,26,7,1,NO_FLAG,"Y:",&oy,1,-5,479-hauteur_fenetre);

  AddChangeValue(&MenuTest,id_spy,1,21,7,1,NO_FLAG,"?:",&spy_var,1,0,9999);

  AddChangeValue(&MenuTest,id_zoom,1,23,7,1,NO_FLAG,"Zoom:",&zoom,1,1,600);
}


void AfficheMenu()
{
  DrawMenu(&MenuTest,0,0);
  Affiche_fenetre();
}


void Affiche_fenetre()
{
    if (zoom==0)
    {
        CopyBlock(ox,oy,ox+largeur_fenetre,oy+hauteur_fenetre,Image,xfen1,yfen1,Log);
    }
    else
    {
        ScaleBox(ox,oy,ox+largeur_fenetre/zoom,oy+hauteur_fenetre/zoom,Image,
                 xfen1,yfen1,xfen2,yfen2,Log);
    }
    CopyBlockPhys(xfen1,yfen1,xfen2,yfen2);
}


void GereMainMenu()
{
  WORD flag=FALSE;
  WORD choix;
  WORD i;
  LONG x,y,k;
  LONG oldx,oldy;

  UWORD touche;

  Cls();
  Flip();
  AfficheMenu();

  Flip();
  while (flag==FALSE)
  {
    AffMouse();

    choix=GereMenu(&MenuTest);
    switch (choix)
    {
    case id_crayon:
        {
            CopyScreen(Image,Log);      // Affiche l'image
            Flip();                     // -> Ecran
            oldx=Mouse_X;
            oldy=Mouse_Y;
            do
            {
                k=Click;
                if (k==1)
                {
                    x=Mouse_X;
                    y=Mouse_Y;
                    Line(x,y,oldx,oldy,couleur);
                    CopyBlockPhys(min(x,oldx),min(y,oldy),max(x,oldx),max(y,oldy));
                    oldx=x;
                    oldy=y;
                }
            }
            while (k!=2);
            CopyScreen(Log,Image);      // Mémorise l'image modifiée
            Cls();
            AfficheMenu();
        }
        break;
    case id_charge:
        {
            *PcxPathname=0;
            if (FileSelector("Chargement PCX","*.PCX",PcxPathname,SELECT_TEST_EXIST))
            {
                Load_Pcx(PcxPathname,Image,pal);
                Palette(pal);
                AfficheMenu();
            }
        }
        break;
    case id_sauve:
        {
            Save_Pcx("test.pcx",Image,pal);
        }
        break;
    case id_xpos:
    case id_ypos:
        {
            AfficheMenu();
        }
        break;
    case id_zoom:
        {
            ChangeChangeValue(&MenuTest,id_xpos,16,0,639-largeur_fenetre/zoom,1);
            ChangeChangeValue(&MenuTest,id_ypos,16,0,479-hauteur_fenetre/zoom,1);
            Affiche_fenetre();
        }
        break;
    case id_quitte:
        {
          CopyScreen(Log,Screen1);
          if (Confirm("Etes vous sur de vouloir quitter ???","OUI","non")==1)
            flag=TRUE;
          CopyScreen(Screen1,Log);
          Flip();
        }
        break;
    }
  }
}


void Quit(char *mess)
{
  ClearKeyboard();
  ClearMouse();
  ClearAdelineSystem();

  if (Screen1)
    Free(Screen1);
  if (Screen2)
    Free(Screen2);
  if (Image)
    Free(Image);

  printf("%s\n",mess);
  exit(0);
}


void main()
{
  int choix;

  InitAdelineSystem("LBA.CFG",INIT_SVGA);
  InitMouse();
  InitKeyboard();

  Screen1=Malloc(640*480+500);
  RazMem(Screen1,640*480+500);
  if (!Screen1)
    Quit("Pas asser de mémoire");
  Screen2=Malloc(640*480+500);
  RazMem(Screen2,640*480+500);
  if (!Screen2)
    Quit("Pas asser de mémoire");
  Image=Malloc(640*480+500);
  RazMem(Image,640*480+500);
  if (!Image)
    Quit("Pas asser de mémoire");

  InitMenus();

  GereMainMenu();

  Quit("Test OK !!!");
}

Just by looking at the code today I can see there's a clear number of defects:
  • It's normally not necessary to test if a pointer is not null before freeing it.
  • The RazMem11 calls should happen after the zero test
  • Comparing to FALSE is generally frowned upon
  • No matter the reason for quitting, the program always returns 0 to the caller
  • The indentation is not the same everywhere
That being said, it's easier to figure out this type of problem in 2021 on a multi-screen configuration, where you can easily see about 100 lines of 256 characters at the same time.

That's all for today

I'm not sure about what I will be able/allow to share, but the idea is to give you a clear idea of how these games were made. I hope that was interesting so far, I have more material to share, but in the mean time, here is a photo of the Adeline Team relaxing at the terrace in a ski resort, probably in 1995 or 1996.
The Adeline team at a ski resort
The Adeline team at a ski resort
And as usual, feel free to ask questions!
Adeline Software
Part 1 Part 2  



1. Insane crunch, financial difficulties, ...
2. Except Frédéric Taquet who used a Silicon Graphics workstation for Softimage 3D work on cut scenes
3. The other mostly had 486 DX2-66, so I was a bit annoyed, but in practice this machine running internally at 50mhz was quite up to the task. It was originally used as a server, and was rock solid. I got a Pentium 60 upgrade when we switched to Windows 95
4. Laurent Salmeron who was working on a CD driver for the new games had a very fancy drive - possibly a NEC MultiSpin - where you could store multiple discs at the same time. He managed to trigger the CD ejection while it was still spinning, breaking in splinters against the wall behind him!
5. Not a very big screen by today's standards, I was lucky that my graphic card supported the 132x50 text mode (possibly even more, but I remember the display to be to fuzzy to be usable)
6. Like for example cleaning personnel deciding to make their work easier by nicely rolling together a mess a cable in one of the rooms, resulting in a nice induction coil blocking the data transfer.
7. Basically divide and conquer: Split the network in two, if one the two is working then you know the issue is in the other half. Rinse and repeat until you found the problem.
8. 320x200 pixels in 256 colors, sometimes referred to as MCGA
9. 640x480 pixels instead of 320x200
10. Z-Soft PC Paintbrush
11. RAZ=Remize A Zero, so RazMem is literally ZeroMemory
comments powered by Disqus