// Program written by Maarten Meuris AKA Nyerguds
program dune_edit;
uses crt, dos;

TYPE
   // For storing layout characters
   chset      = array [1..8] of byte;
   // For coordinates of layout frames
   coords     = array [1..4] of integer;
   // For storing a 4-byte value as booleans
   boollist   = array [0..32] of boolean;
   // For storing a 4-byte value as bytes
   // ALWAYS USE AS BIG-ENDIAN!!! The read & write methods convert it to correspond to the little-endian in the exe file
   fourbyte   = array [0..3] of byte;

   // For editing description for each data type
   descr      = array [1..2] of string[36];
   // For help description for each data type
   bigdescr   = array [1..7] of string[48];

   // Type designed for data types where the list index in the editor does not correspond to the internal value
   values = Record
      val:integer;
      txt:string[19];
   end;

   // Input key codes - "endk" is the [END] key
   keymapper = Record
      up, down, left, right, space, enter, esc, back, pgup, pgdn, home, endk, f1, f2, f3, f4, f11, f12 :char;
   end;

   // Type for the data identification table
   option = Record
      typeid: Integer;
      text: String[20];
   end;

   // put in specific types to have a single type for the unit and structure lists
   lev2Lst = array[0..39] of String;
   lev3Lst = array[0..59] of Option;

var
    // game version / which list is active / level to return to after level #4 / option list lengths (calculated on init)
    gamever,level,prevlev,unitOptsListlen,structOptsListlen: integer;
    // Binary editing mode enabled / Hexadecimal display mode enabled
    binEd,HexDisplay:boolean;
    // File to edit
    exefile:file;
    // input
    keyread:char;
    // Error message when editing fails or cancels
    errormsg:String;
    // navigation & scrolling data for all 4 levels
    select,scroll,maxdisplay,listlen:array [1..4] of integer;
    // Display lists to print on screen
    lev2List,lev4List:lev2Lst;
    lev3List:lev3Lst;

const
    // Input key codes
    keymap:keymapper = ( up:    char(072); down:  char(080);
                         left:  char(075); right: char(077);
                         space: char(032); enter: char(013);
                         esc:   char(027); back:  char(008);
                         pgup:  char(073); pgdn:  char(081);
                         home:  char(071); endk:  char(079);
                         f1:    char(059); f2:    char(060);
                         f3:    char(061); f4:    char(062);
                         f11:   char(133); f12:   char(134));
    // General info Strings
    editorname:string[14] = 'Dune II Editor';
    editorversion:string[6] = 'v1.8';
    editorauthor:string[8] = 'Nyerguds';
    gameverstr:String[12] = 'EXE version:';
    gamenamestr:String[9] = 'DUNE2.EXE';

    // == VERSION CONTROL ==
    // When adding a version, all array lengths must be updated.
    // String to display for each version
    dunever:   array[0..4] of string[7] = ('DEMO','1.00','1.07-US','1.07-EU','1.07-HS');
    //place of the identification string (gamenamestr) in each version
    verAddress:array[0..4] of integer = (225278,229282,228274,229682,229586);
    // Correction to make file addresses out of exe references for each version
    refAddress:array[0..4] of fourbyte = (($32, $4D, $90, $F0),
                                          ($33, $2B, $81, $10),
                                          ($32, $EC, $85, $00),
                                          ($33, $44, $7F, $80),
                                          ($33, $3E, $7F, $E0));
    // Start offset of unit/struct data for each version
    unitOptsOffset:  array[0..4] of integer= (201840,198480,195760,195840,195760);
    structOptsOffset:array[0..4] of integer= (199930,196570,193930,194010,193930);


    // DOS ASCII character codes for drawing frames; the two types are Active and Inactive
    framechar:array[1..2] of chset = (
                       //  |"  "|   |_  _|     -    |   "|"  _|_
                         ($DA, $BF, $C0, $D9, $C4, $B3, $C2, $C1),
                         ($C9, $BB, $C8, $BC, $CD, $BA, $CB, $CA));

    // Coordinates of layout frames
    selectBoundaries:array[1..4] of coords = (
                         (3,4,25,5), (3,9,25,23), (32,4,54,23), (17,12,39,18));

    // Editing description for each data type
    typedescr:array[0..11] of descr = (
       ('Boolean (Yes/No)                   ',''),
       ('Single byte (-128,127)             ',''),
       ('Double byte (-32768,32767)         ',''),
       ('Reference to 0-terminated string.  ','Give decimal offset in "DUNE2.EXE".'),
       ('Ownership. Press space to select or','deselect a side.                   '),
       ('Sidebar command. Select one from   ','the list.                          '),
       ('Movement type.  Select one from the','list.                              '),
       ('Weapon type. Select one from the   ','list.                              '),
       ('Foundation dimensions. Select one  ','from the list.                     '),
       ('Construction option. Select one    ','unit from the list.                '),
       ('Prerquisite structures. Press space','to select or deselect a structure. '),
       ('Weapon Sounds. Select one from the ','list.                              '));

    // Help info for each data type
    typehelp:array[0..11] of bigdescr = (
    //  [________________________________________________] = max length for each string
       ('BOOLEANS are simple Yes/No values.','','','','','',''),
       ('SINGLE BYTES range from -128 to 127.','','','','','',''),
       ('DOUBLE BYTES are the most common data type.','They range from -32768 to 32767.','','','','',''),
       ('CODE REFERENCES are internal references which','point to a zero-terminated string. The editor ','converts these value to normal file addresses.','','Be VERY careful when editing these; changing','name references can have disastrous results.','Both this editor and the game rely on these.'),
       ('The OWNERSHIP value is a list of switches that','allow or deny specific houses the right to build','this specific unit or structure.','','','',''),
       ('The SIDEBAR COMMANDS allow players to give','instructions to units. Note that these command','buttons may show weird behaviour if they are on','a different place than they are meant to be.','','After a move or attack command is completed, the','fourth command is automatically executed.'),
       ('The MOVEMENT TYPE determines a lot of the unit''s','behaviour on the battlefield. Each movement type','has its own unique characteristics, like how ','units on foot can be crushed by tracked units,','and slithering units can''t move on rock.','',''),
       ('The WEAPON TYPE is the projectile the unit will','fire. Since all projectiles are units, all units','appear in the selection list. However, using','normal units as projectiles doesn''t seem to','work in the game.','',''),
       ('The FOUNDATION determines the amount of cells a','building takes, and which shape they are in.','','Note that changing the foundations of the','Concrete Slabs can give very unexpected results.','',''),
       ('Choosing a CONSTRUCTION OPTION gives a building','a unit to add to its production options list.','','','','',''),
       ('The PREREQUISITE buildings are the buildings a','player needs to have before he can build this','unit or building.','','','',''),
       ('SOUNDS are played when a unit attacks or when a','weapon is fired.','The sounds indicated with "VOC" have a .VOC file','that is played. The rest only give generated','sounds.','',''));


    // Strings for specific data types
    booleans:array[0..1] of string[3] = ('No','Yes');
    owners:array[0..5] of String[9] = ('Harkonnen','Atreides','Ordos',
                                       'Fremen','Sardaukar','Mercenary');
    movementTypes:array[0..5] of string[9] = ('Foot','Tracked','Harvester',
                                              'Wheeled','Flying','Slither');
    commands:array[0..13] of string[10] = ('Attack','Move','Retreat','Guard',
                                          'Area Guard','Harvest','Return','Stop',
                                          'Ambush','Sabotage','Die','Hunt',
                                          'Deploy','Destruct');
    foundations:array[0..6] of string[3] = ('1x1','2x1','1x2','2x2','2x3','3x2','3x3');
    sounds:array[0..35] of values = (
                                      (val:-1; txt: 'None               '),
                                      (val: 0; txt: 'Not Applicable     '),
                                      (val:20; txt: 'Place down         '),
                                      (val:22; txt: 'Twinkling          '),
                                      (val:23; txt: 'Intro chord        '),
                                      (val:24; txt: 'Carryall dropoff   '),
                                      (val:30; txt: 'Inf kill 1      VOC'),
                                      (val:35; txt: 'Inf kill 2      VOC'),
                                      (val:33; txt: 'Inf kill 3      VOC'),
                                      (val:34; txt: 'Inf kill 4      VOC'),
                                      (val:31; txt: 'Inf kill 5      VOC'),
                                      (val:36; txt: 'Inf crush       VOC'),
                                      (val:38; txt: 'Click           VOC'),
                                      (val:39; txt: 'Missile         VOC'),
                                      (val:40; txt: 'Hit sand        VOC'),
                                      (val:41; txt: 'Tank shot       VOC'),
                                      (val:42; txt: 'Rocket          VOC'),
                                      (val:43; txt: 'Sonic wave         '),
                                      (val:44; txt: 'Struct crumble  VOC'),
                                      (val:46; txt: 'Death Hand target  '),
                                      (val:47; txt: 'Cannot place beep  '),
                                      (val:49; txt: 'Tank shot       VOC'),
                                      (val:55; txt: 'Tank shot       VOC'),
                                      (val:51; txt: 'Exploding unit  VOC'),
                                      (val:52; txt: 'Money increase     '),
                                      (val:53; txt: 'Money decrease     '),
                                      (val:54; txt: 'Weapon hit         '),
                                      (val:56; txt: 'Tank shot       VOC'),
                                      (val:57; txt: 'Tank shot       VOC'),
                                      (val:58; txt: 'Single gun      VOC'),
                                      (val:59; txt: 'Machinegun      VOC'),
                                      (val:60; txt: '"Constr Compl"  VOC'),
                                      (val:61; txt: 'Plingplong         '),
                                      (val:62; txt: 'Radar static    VOC'),
                                      (val:63; txt: 'Worm attack     VOC'),
                                      (val:64; txt: 'Mini Rocket     VOC')
                                      );

    // Main list settings
    mainlistlen:integer=1;
    mainlist:array[0..1] of String[20] = ('Units       ',
                                          'Structures  ');
    // Number of units & structures - 1 , since first element is #0)
    unitlistlen:integer=26;
    structlistlen:integer=18;

    // Option lists for units/structs
    // Needs to be adjusted to 60 elements with typeid:-1 values to correspond to the lev3Lst type.
    unitOptsList:lev3Lst = (
              (typeid: 2; text: 'Short name ID       '),
              (typeid: 3; text: 'Name code ref.      '),
              (typeid: 2; text: 'Long name ID        '),
              (typeid: 3; text: 'WSA file code ref.  '),
              (typeid: 2; text: 'Unknown 005         '),
              (typeid: 2; text: 'Unknown 006         '),
              (typeid: 2; text: 'Hit points          '),
              (typeid: 2; text: 'Sight               '),
              (typeid: 2; text: 'Sidebar icon gfxID  '),
              (typeid: 2; text: 'Cost                '),
              (typeid: 2; text: 'Build Time          '),
              (typeid: 2; text: 'Tech level          '),
              (typeid:10; text: 'Prerequisites       '),
              (typeid: 1; text: 'Unknown 014         '),
              (typeid: 1; text: 'Upgrades needed     '),
              (typeid: 5; text: 'Sidebar command #1  '),
              (typeid: 5; text: 'Sidebar command #2  '),
              (typeid: 5; text: 'Sidebar command #3  '),
              (typeid: 5; text: 'Sidebar command #4  '),
              (typeid: 2; text: 'Unknown 020         '),
              (typeid: 1; text: 'Unknown 021         '),
              (typeid: 2; text: 'Unknown 022         '),
              (typeid: 2; text: 'Unknown 023         '),
              (typeid: 4; text: 'Owner               '),
              (typeid: 2; text: 'Unknown 025         '),
              (typeid: 2; text: 'Unknown 026         '),
              (typeid: 1; text: 'Unknown 027         '),
              (typeid: 1; text: 'Unknown 028         '),
              (typeid: 1; text: 'Unknown 029         '),
              (typeid: 1; text: 'Unknown 030         '),
              (typeid: 2; text: 'Unknown 031         '),
              (typeid: 6; text: 'Movement type       '),
              (typeid: 2; text: 'Unknown 033         '),
              (typeid: 2; text: 'Speed               '),
              (typeid: 2; text: 'Unknown 035         '),
              (typeid: 2; text: 'Unit gfxID          '),
              (typeid: 2; text: 'Turret gfxID        '),
              (typeid: 2; text: 'Unknown 038         '),
              (typeid: 2; text: 'Unknown 039         '),
              (typeid: 2; text: 'Unknown 040         '),
              (typeid: 2; text: 'Weapon rate of fire '),
              (typeid: 2; text: 'Weapon range        '),
              (typeid: 2; text: 'Weapon damage       '),
              (typeid: 2; text: 'Unknown 044         '),
              (typeid: 7; text: 'Weapon type         '),
              (typeid:11; text: 'Weapon sound        '),
              (typeid:-1; text: ''), (typeid:-1; text: ''),
              (typeid:-1; text: ''), (typeid:-1; text: ''),
              (typeid:-1; text: ''), (typeid:-1; text: ''),
              (typeid:-1; text: ''), (typeid:-1; text: ''),
              (typeid:-1; text: ''), (typeid:-1; text: ''),
              (typeid:-1; text: ''), (typeid:-1; text: ''),
              (typeid:-1; text: ''), (typeid:-1; text: ''));

    structOptsList:lev3Lst = (
              (typeid: 2; text: 'Short name ID       '),
              (typeid: 3; text: 'Name code ref.      '),
              (typeid: 2; text: 'Long name ID        '),
              (typeid: 3; text: 'WSA file code ref.  '),
              (typeid: 2; text: 'Unknown 005         '),
              (typeid: 2; text: 'Unknown 006         '),
              (typeid: 2; text: 'Hit points          '),
              (typeid: 2; text: 'Sight               '),
              (typeid: 2; text: 'Sidebar icon gfxID  '),
              (typeid: 2; text: 'Cost                '),
              (typeid: 2; text: 'Build Time          '),
              (typeid: 2; text: 'Tech level          '),
              (typeid:10; text: 'Prerequisites       '),
              (typeid: 1; text: 'Build order         '),
              (typeid: 1; text: 'CY upgrades needed  '),
              (typeid: 1; text: 'Unknown 018         '),
              (typeid: 1; text: 'Unknown 019         '),
              (typeid: 1; text: 'Unknown 020         '),
              (typeid: 1; text: 'Unknown 021         '),
              (typeid: 1; text: 'Unknown 022         '),
              (typeid: 1; text: 'Unknown 023         '),
              (typeid: 1; text: 'Unknown 024         '),
              (typeid: 1; text: 'Unknown 025         '),
              (typeid: 1; text: 'Unknown 026         '),
              (typeid: 2; text: 'Structure ID        '),
              (typeid: 2; text: 'Weapon damage       '),
              (typeid: 2; text: 'Unknown 027         '),
              (typeid: 4; text: 'Owner               '),
              (typeid: 2; text: '(repair facility)   '),
              (typeid: 0; text: 'Units can enter     '),
              (typeid: 2; text: 'Spice storage       '),
              (typeid: 2; text: 'Power consumed      '),
              (typeid: 8; text: 'Foundation size     '),
              (typeid: 2; text: 'Unknown 036         '),
              (typeid: 2; text: 'Unknown 037         '),
              (typeid: 1; text: '(building graphics) '),
              (typeid: 1; text: '(building graphics) '),
              (typeid: 2; text: 'Unknown 040         '),
              (typeid: 1; text: '(building graphics) '),
              (typeid: 1; text: '(building graphics) '),
              (typeid: 2; text: 'Unknown 043         '),
              (typeid: 1; text: '(building graphics) '),
              (typeid: 1; text: '(building graphics) '),
              (typeid: 9; text: 'Constr. option #1   '),
              (typeid: 9; text: 'Constr. option #2   '),
              (typeid: 9; text: 'Constr. option #3   '),
              (typeid: 9; text: 'Constr. option #4   '),
              (typeid: 9; text: 'Constr. option #5   '),
              (typeid: 9; text: 'Constr. option #6   '),
              (typeid: 9; text: 'Constr. option #7   '),
              (typeid: 9; text: 'Constr. option #8   '),
              (typeid: 2; text: '1st upgrade techlvl '),
              (typeid: 2; text: '2nd upgrade techlvl '),
              (typeid: 2; text: '3rd upgrade techlvl '),
              (typeid:-1; text: ''),(typeid:-1; text: ''),
              (typeid:-1; text: ''),(typeid:-1; text: ''),
              (typeid:-1; text: ''),(typeid:-1; text: ''));

// =========================================
//                 MISCELLANEOUS
// =========================================

// Press Key To Continue procedure with buffer clear.
procedure cntin(showMsg: boolean);
begin
   if showMsg then
    begin
     writeln();
     writeln();
     write('Press any key to continue. . . ');
    end;
   repeat until keypressed;
    ReadKey();
   while keypressed do ReadKey(); {Clears the keyboard buffer}
end; {cntin}

procedure cntin();
begin
  cntin(false);
end; {cntin}


// Sets cursor to end of page to 'hide' it.
procedure resetCursor();
begin
  gotoxy(80,25);
end;

function uppercase(str:string):String;
var i:integer;
begin
   for i:=1 to length(str) do
     if (byte(str[i])>=97) and (byte(str[i])<=122) then str[i]:=char(byte(str[i])-32);
   uppercase:=str;
end;

function lowercase(str:string):String;
var i:integer;
begin
   for i:=1 to length(str) do
     if (byte(str[i])>=65) and (byte(str[i])<=90) then str[i]:=char(byte(str[i])+32);
   lowercase:=str;
end;

function isNumeric(s:string):boolean;
Var
  val,i: integer;
begin
  isNumeric:=true;
  for i:=1 to length(s) do
   begin
    val:=byte(s[i])-48;
    if (val<0) or (val>9) then isNumeric:=false;
   end;
end;

// For abbreviating prerequisites to 4 characters
function smartAbbrev(toAbbrev:string):string;
begin
  smartAbbrev:='';
  if length(toAbbrev)>4 then
   begin
     if pos(' ',toAbbrev)>2 then
       smartAbbrev:=copy(toAbbrev,1,2) + copy(toAbbrev,pos(' ',toAbbrev)+1,2)
     else if pos(' ',toAbbrev)=2 then
       smartAbbrev:=copy(toAbbrev,1,1) + copy(toAbbrev,pos(' ',toAbbrev)+1,3)
     else if pos('-',toAbbrev)>2 then
       smartAbbrev:=copy(toAbbrev,1,2) + copy(toAbbrev,pos('-',toAbbrev)+1,2)
     else if pos('-',toAbbrev)=2 then
       smartAbbrev:=copy(toAbbrev,1,1) + copy(toAbbrev,pos('-',toAbbrev)+1,3)
     else if not isNumeric(Copy(toAbbrev,length(toAbbrev)-3, 3)) then
      begin
        if isNumeric(Copy(toAbbrev, length(toAbbrev)-2, 2)) then
          smartAbbrev:=copy(toAbbrev,1,2) + Copy(toAbbrev, length(toAbbrev)-1, 2)
        else if isNumeric(toAbbrev[length(toAbbrev)]) then
          smartAbbrev:=copy(toAbbrev,1,3) + Copy(toAbbrev, length(toAbbrev), 1)
        else smartAbbrev:=copy(toAbbrev,1,4);
      end
     else smartAbbrev:=copy(toAbbrev,1,4);
   end
  else smartAbbrev:=toAbbrev;
end;

// To convert value to string on the fly without needing a variable to store it in.
function toString(b:byte):String;
begin
  str(b,toString);
end;

function toString(i:Integer):String;
begin
  str(i,toString);
end;

// General data display functions. Keep in mind that "fourbyte" is big-endian.

function fourbyte2int(data:fourbyte):integer;
var i:integer;
begin
  fourbyte2int:=0;
  for i:=0 to 3 do
    fourbyte2int:=fourbyte2int*$100+data[i];
end;

function int2fourbyte(data:integer):fourbyte;
var data2,i:integer;
begin
  data2:=data;
  for i:=3 downto 0 do
    begin
     if (data<0) and (data2=0) then int2fourbyte[i]:=$FF
     else int2fourbyte[i]:=data2 mod $100;
     data2:=data2 div $100;
    end;
end;

function hexstr2byte(hex:string[2]):byte;
var i,value,multiplier:integer;
begin
  hexstr2byte:=0;
  multiplier:=16;
  if (length(hex)=0) then hex:='00';
  if (length(hex)=1) then hex:=concat('0' + hex);
  for i:=1 to 2 do
   begin
     if (byte(hex[i])>=65) and (byte(hex[i])<=70) then value:=byte(hex[i])-55
     else if (byte(hex[i])>=48) and (byte(hex[i])<=57) then value:=byte(hex[i])-48
     else value:=-1;
     if (value>=0) then
      begin
        hexstr2byte:=hexstr2byte+value*multiplier;
        multiplier:=1;
      end
     else
      begin
        i:=2;
        hexstr2byte:=0;
      end;
   end;
end;

function byte2hexstr(hex:byte):string;
begin
  byte2hexstr:='';
  if ((hex div 16) >=10) then byte2hexstr:=byte2hexstr+char((hex div 16)+55)
  else byte2hexstr:=byte2hexstr+char((hex div 16)+48);
  if ((hex mod 16) >=10) then byte2hexstr:=byte2hexstr+char((hex mod 16)+55)
  else byte2hexstr:=byte2hexstr+char((hex mod 16)+48);
end;

function byte2binstr(bin:byte;split:boolean):string;
var
    i:integer;
begin
  byte2binstr:='';
  for i:=0 to 7 do
   begin
     byte2binstr:=toString(bin mod 2) + byte2binstr;
     bin:=bin div 2;
     if (i=3) and split then byte2binstr:=' '+byte2binstr;
   end;
end;

// =========================================
//              BARE NAVIGATION
// =========================================
procedure MoveDown(lines:integer);
begin
  if lines>0 then
   begin
     if ((select[level]+lines)<maxdisplay[level]) then
        // normal move
        select[level]:=select[level]+lines
     else
      begin
        // move beyond visible field
        lines:=lines-(maxdisplay[level]-select[level]);
        select[level]:=maxdisplay[level];
       end;
     if (lines>0) and (select[level]=maxdisplay[level]) and (maxdisplay[level]<listlen[level]) then
      begin
      // scroll
        scroll[level]:=scroll[level]+lines;
        if scroll[level] > (listlen[level]-maxdisplay[level]) then scroll[level]:=(listlen[level]-maxdisplay[level]);
      end;
  end;
end;

procedure MoveUp(lines:integer);
begin
  if lines>0 then
   begin
     if ((select[level]-lines)>0) then
       // move
       select[level]:=select[level]-lines
     else
       begin
       // move beyond visible field
        lines:=lines-select[level];
        select[level]:=0;
       end;
     if (lines>0) and (select[level]=0) and (scroll[level]>=0) then
       // scroll
       begin
         scroll[level]:=scroll[level]-lines;
         if scroll[level] < 0 then scroll[level]:=0;
       end;
   end;
end;

procedure MoveRight();
begin
  if level=3 then level:=1
  else level:=level+1;
end;

procedure MoveLeft();
begin
  if level=1 then level:=3
  else level:=level-1;
end;


// =========================================
//                 INPUT / OUTPUT
// =========================================

function openFile(filename:String) : boolean;
var
  status:integer;
begin
  assign(exefile,filename);
  {$I-}
  reset(exefile,1);
  status:=IOResult;
  if status<>0 then
   begin
    writeln('Error opening "',filename,'".');
    openFile:=false;
    {$I+}
   end
  else openFile:=true;
end; {openFile}

procedure closeFile();
begin
  close(exefile);
  {$I+}
end; {closeFile}

function readByte(address:Integer):byte;
var
  actual:word;
begin
  seek(exefile,address);
  blockread (exefile,readByte,1,actual);
end;

// converts little-endian to big-endian
function readBytes(address:integer;nrofbytes:byte):fourbyte;
var i:byte;
begin
  if nrofbytes>4 then nrofbytes:=4;
  for i:=0 to 3 do
    readBytes[i]:=0;
  for i:=3 downto (4-nrofbytes) do
   begin
     readBytes[i]:=readByte(address);
     address:=address+1;
   end;
end;

function readBytesInt(address:integer;nrofbytes:byte):integer;
begin
  readBytesInt:=fourbyte2int(readBytes(address,nrofbytes));
end;

procedure writeByte(address:Integer;data:byte);
var
  actual:word;
begin
  seek(exefile,address);
  blockwrite(exefile,data,1,actual);
end;


// converts big-endian to little-endian
procedure writeBytes(address:Integer;data:fourbyte;nrofbytes:byte);
var
  i:byte;
begin
  if nrofbytes>4 then nrofbytes:=4;
  for i:=3 downto (4-nrofbytes) do
   begin
     writeByte(address,data[i]);
     address:=address+1;
   end;
end;

procedure writeBytesInt(address,data:Integer;nrofbytes:byte);
begin
  writeBytes(address,int2fourbyte(data),nrofbytes);
end;


// =========================================
//              DATA & OFFSETS
// =========================================

// DATA TYPE CASE - Defines length for each data type. VERY important.
function getDataLen(opttype:integer):integer;
begin
  case opttype of
  -1 : getDataLen:=0;  // Safety value for empty list item.
   0 : getDataLen:=2;  // Boolean (2 byte)
   1 : getDataLen:=1;  // 1-byte
   2 : getDataLen:=2;  // 2-byte
   3 : getDataLen:=4;  // 4-byte (REF)
   4 : getDataLen:=1;  // owner
   5 : getDataLen:=2;  // command
   6 : getDataLen:=2;  // Movement type
   7 : getDataLen:=2;  // weapon type
   8 : getDataLen:=2;  // foundation
   9 : getDataLen:=2;  // construction option
  10 : getDataLen:=4;  // structures
  11 : getDataLen:=2;  // sounds
   end;
end;

function getDataLen(typeindex,optindex:integer):integer;
var
    optslist:lev3lst;
begin
  if typeindex=0 then optslist:=unitOptsList
  else optslist:=structOptsList;
  getDataLen:=getDataLen(optslist[optindex].typeid)
end;

function getListLen(typeindex,toindex:integer):integer;
var i:integer;
    optslist:lev3lst;
begin
  if typeindex=0 then optslist:=unitOptsList
  else optslist:=structOptsList;
  getListLen:=0;
  for i:=0 to toindex-1 do
  begin
    getListLen:=getListLen+getDataLen(optslist[i].typeid);
  end;
end;

function getAddress(typeindex,unitindex,optindex:integer):integer ;
var typeoptsOffset,optslistLen:integer;
begin
  if typeindex=0 then begin optslistLen:=unitOptsListLen; typeoptsOffset:=unitOptsOffset[gamever]; end
  else begin optslistLen:=structOptsListLen;typeoptsOffset:=structOptsOffset[gamever]; end;
  getAddress:=typeoptsOffset + (unitindex*getListLen(typeindex,optslistLen+1)) + getListLen(typeindex,optindex);
end;

// Get offset from reference
function getRefOffset(inputhex:fourbyte):integer;
var multiplier,i:integer;
begin
  getRefOffset:=0;
  multiplier:=1;
  for i:=3 downto 0 do
   begin
     getRefOffset:=getRefOffset+((inputhex[i]-refAddress[gamever][i])*multiplier);
     multiplier:=multiplier*256;
   end;
end;

// Get reference from offset
function getOffsetRef(inputint:integer):fourbyte;
var
    i:integer;
begin
  if inputint<>0 then
   begin
     getOffsetRef[0]:=refAddress[gamever][0];
     inputInt:=inputInt+refAddress[gamever][1]*256*256 + refAddress[gamever][2]*256 + refAddress[gamever][3];
     for i:=3 downto 1 do
      begin
        getOffsetRef[i]:=inputInt mod 256;
        inputInt:=inputInt div 256;
      end;
   end
  else
   for i:=3 downto 0 do
    begin
      getOffsetRef[i]:=0;
    end;
end;

// Reads zero-terminated string
function readString(address:integer):string;
var c:byte;
    i:integer;
begin
  i:=0;
  c:=$FF;
  readString:='';
  while (c<>0) and (i<18) do
   begin
     c:=readByte(address+i);
     if c<>0 then readString:=concat(readString+char(c));
     i:=i+1;
   end;
end;

// Gets offset from reference with zero and EOF correction.
function getRefAddress(inputhex:fourbyte):integer;
var
    blank:boolean;
    i:integer;
begin
  getRefAddress:=getRefOffset(inputhex);
  blank:=true;
  for i:=0 to 3 do
    if inputHex[i]<>0 then blank:=false;
  if blank then getRefAddress:=0;
  if (getRefAddress<0) or (getRefAddress> filesize(exefile)) then getRefAddress:=-1;
end;

function getRefAddress(typeindex,unitindex,optindex:integer):integer;
begin
  getRefAddress:=getRefAddress(readBytes(getAddress(typeindex,unitindex,optindex),4));
end;

// =========================================
//             DATA TYPE DISPLAY
// =========================================

// Reads zero-terminated string defined by a reference
function getRefString(inputHex:fourbyte):string;
var
    blank:boolean;
    i:integer;
    ref:integer;
begin
  ref:=getRefOffset(inputhex);
  i:=0;
  blank:=true;
  for i:=0 to 3 do
    if inputHex[i]<>0 then blank:=false;

  if (ref>0) and not blank then getRefString:=readString(ref)
  else if blank then getRefString:='<no reference>'
  else getRefString:='<invalid ref>';
end;

function getRefString(typeindex,unitindex,optindex:integer):string;
begin
  getRefString:=getRefString(readBytes(getAddress(typeindex,unitindex,optindex),4));
end;

function getBytesString(nrofbytes:byte;inputhex:fourbyte;split:boolean):string;
var first,i:integer;
begin
  getBytesString:='';
  if nrofbytes<=4 then first:=4-nrofbytes
  else first:=3;
  for i:=first to 3 do
   begin
     getBytesString:=getBytesString + byte2hexStr(inputhex[i]);
     if (i<>3) and split then getBytesString:=getBytesString + ' ';
   end;
end;

function getBytesString(typeindex,unitindex,optindex:integer;split:boolean):string;
begin
  getBytesString:=getBytesString(getDataLen(typeindex,optindex),readBytes(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex)),split);
end;

function getBinString(nrofbytes:byte;inputhex:fourbyte;split:boolean):string;
var last,i:integer;
begin
  getBinString:='';
  if nrofbytes<=4 then last:=4-nrofbytes
  else last:=3;
  for i:=last to 3 do
   begin
     getBinString:=getBinString + byte2binStr(inputhex[i],split);
     if (i<>3) and split then getBinString:=getBinString + ' | ';
   end;
end;

function getBinString(typeindex,unitindex,optindex:integer;split:boolean):string;
begin
  getBinString:=getBinString(getDataLen(typeindex,optindex),readBytes(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex)),split);
end;

{
//unsigned
function getByte(typeindex,unitindex,optindex:integer):byte;
begin
  getByte:=readByte(getAddress(typeindex,unitindex,optindex));
end;
}

//signed
function getBytes(typeindex,unitindex,optindex,nrofbytes:integer):integer;
var i,corrvalue:integer;
begin
  getBytes:=readBytesInt(getAddress(typeindex,unitindex,optindex),nrofbytes);
  corrvalue:=$7F;
  for i:=2 to nrofbytes do
    corrvalue:=corrvalue*$100+$FF;
  if (getBytes>corrvalue) then getBytes:=getBytes-((corrvalue+1)*2);
end;


function getMovementTypeStr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=getBytes(typeindex,unitindex,optindex,2);
  if (i>=0) and (i<=5) then
    getMovementTypestr:=movementTypes[i]
  else getMovementTypestr:='<Invalid type>';
end;

function getSoundStr(typeindex,unitindex,optindex:integer):string;
var
  data,i:integer;
begin
  data:=getBytes(typeindex,unitindex,optindex,2);
  getSoundstr:='<Unknown>';
  for i:=0 to 35 do
   if data=sounds[i].val then getSoundstr:=sounds[i].txt;
end;

function getCommandStr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=getBytes(typeindex,unitindex,optindex,2);
  if (i>=0) and (i<=13) then
    getCommandstr:=commands[i]
  else getCommandstr:='<Invalid type>';
end;

function getFoundationStr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=getBytes(typeindex,unitindex,optindex,2);
  if (i>=0) and (i<=6) then
    getFoundationstr:=foundations[i]
  else getFoundationstr:='<Invalid type>';
end;

function UnitType(i:integer):string;
begin
  if i=-1 then UnitType:='None'
  else UnitType:=getRefString(0,i,1);
end;

function StructureType(i:integer):string;
begin
  if i=-1 then StructureType:='None'
  else StructureType:=getRefString(1,i,1);
end;

function getUnitTypestr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=getBytes(typeindex,unitindex,optindex,2);
  getUnitTypestr:=UnitType(i);
end;

function readPrerequisites(len,typeindex,unitindex,optindex:integer):boollist;
var
    val,i,j:integer;
begin
  for i:=0 to 32 do readPrerequisites[i]:=false;
  val:=0;
  for i:=0 to len-1 do
   begin
     val:=readBytesInt(getAddress(typeindex,unitindex,optindex)+i,2);
     for j:=0 to 7 do
      begin
        if (val mod 2) = 1 then readPrerequisites[i*8+j]:=true;
        val:=val div 2;
      end;
   end;
end;

procedure writePrerequisites(len:integer;p:boollist;typeindex,unitindex,optindex:integer);
var
    modifier,i,j:integer;
    val:byte;
begin
  val:=0;
  for i:=0 to len-1 do
   begin
     modifier:=1;
     val:=0;
     for j:=0 to 7 do
      begin
        if p[i*8+j] then val:=val+modifier;
        modifier:=modifier*2;
      end;
     writeByte(getAddress(typeindex,unitindex,optindex)+i,val);
   end;
end;

function getPrerequisitestr(len,typeindex,unitindex,optindex:integer):string;
var
    b:boollist;
    i:integer;
begin
  b:=readPrerequisites(len,typeindex,unitindex,optindex);
  getPrerequisitestr:='';
  for i:=0 to 18 do
    if b[i] then getPrerequisitestr:=getPrerequisitestr + smartAbbrev(StructureType(i)) + ',';
  if length(getPrerequisitestr)>0 then getPrerequisitestr:=copy(getPrerequisitestr,1,length(getPrerequisitestr)-1);
  if length(getPrerequisitestr)>19 then getPrerequisitestr:=concat(copy(getPrerequisitestr,1,17)+'..');
  if length(getPrerequisitestr)=0 then getPrerequisitestr:='(none)';

end;


function readOwner(typeindex,unitindex,optindex:integer):boollist;
begin;
   readOwner:=readPrerequisites(1,typeindex,unitindex,optindex);
end;

procedure writeOwners(o:boollist;typeindex,unitindex,optindex:integer);
begin;
   writePrerequisites(1,o,typeindex,unitindex,optindex);
end;

function getOwnerstr(typeindex,unitindex,optindex:integer):string;
var
    o:boollist;
    i:integer;
begin
  o:=readOwner(typeindex,unitindex,optindex);
  getOwnerstr:='';
  for i:=0 to 5 do
    if o[i] then getOwnerstr:=getOwnerstr+ copy(owners[i],1,2) + ',';
  if length(getOwnerstr)>0 then getOwnerstr:=copy(getOwnerstr,1,length(getOwnerstr)-1)
  else getOwnerstr:='(none)';
end;

function getBooleanstr(typeindex,unitindex,optindex:integer):string;
var
    i:integer;
begin
  i:=readByte(getAddress(typeindex,unitindex,optindex));
  if (i=0) or (i=1) then getBooleanstr:=booleans[i]
  else getBooleanstr:='<Invalid>';
end;

// DATA TYPE CASE - gets correct strings to display in second column of 3rd list
function getDataStr(typeindex,unitindex,optindex:integer):string;
begin
  getDataStr:='';
  case lev3list[optindex].typeid of
   0 : getDataStr:=getBooleanstr(typeindex,unitindex,optindex);                 // boolean
   1 : getDataStr:=tostring(getBytes(typeindex,unitindex,optindex,1));          // 1-byte
   2 : getDataStr:=toString(getBytes(typeindex,unitindex,optindex,2));          // 2-byte
   3 : getDataStr:=getRefString(typeindex,unitindex,optindex);                  // 4-byte (REF)
   4 : getDataStr:=getOwnerstr(typeindex,unitindex,optindex);                   // owner
   5 : getDataStr:=getCommandStr(typeindex,unitindex,optindex);                 // command
   6 : getDataStr:=getMovementTypestr(typeindex,unitindex,optindex);            // movement type
   7 : getDataStr:=getUnitTypestr(typeindex,unitindex,optindex);                // weapon type
   8 : getDataStr:=getFoundationStr(typeindex,unitindex,optindex);              // foundation
   9 : getDataStr:=getUnitTypestr(typeindex,unitindex,optindex);                // construction option
  10 : getDataStr:=getPrerequisitestr(4,typeindex,unitindex,optindex);          // structures
  11 : getDataStr:=getsoundStr(typeindex,unitindex,optindex);                   // sounds

   end;
end;


function inputValue(inputstr:String;minval,maxval:integer):integer;
var charin: char;
    minlen,maxlen:integer;
    tempstr:string;
begin
   charin:=char(0);
   str(minval,tempstr);
   minlen:=Length(tempstr);
   str(maxval,tempstr);
   maxlen:=Length(tempstr);
   write(inputstr);
   while ((charin<>keymap.esc) and (charin<>keymap.F2) and (charin<>keymap.F3) and (charin<>keymap.F4) and (charin<>keymap.enter)) do
   begin
    charin:=ReadKey();
    if ((byte(charin)=45) and (Length(inputstr)=0) and (minval<0))
        or ((byte(charin)>=48) and (byte(charin)<=57)
          and (    ((Length(inputstr)<minlen) and (Copy(inputstr,1,1)='-'))
                or ((Length(inputstr)<maxlen) and (Copy(inputstr,1,1)<>'-'))
              )
       ) then
     begin
       inputstr:=inputstr+charin;
       write(charin);
     end
    else if (charin=keymap.back) and (Length(inputstr)>0) then
     begin
       inputstr:=Copy(inputstr, 1, (Length(inputstr)-1));
       write(keymap.back);
       write('_');
       write(keymap.back);
     end;
   end;
   val(inputstr,inputvalue);
   errormsg:='';
   if ((charin=keymap.esc) or (charin=keymap.F2) or (charin=keymap.F3) or (charin=keymap.F4)) then errormsg:='Canceled'
   else if (inputvalue>maxval) then errormsg:='Value too large!'
   else if (inputvalue<minval) then errormsg:='Value too small!'
end; {inputValue}

function inputValue(minval,maxval:integer):integer;
begin
  inputValue:=inputValue('',minval,maxval);
end;

function inputHexBytes(inputstr:String;nrofbytes:byte):fourbyte;
var charin: char;
    i:integer;
begin
   for i:=0 to 3 do
     inputHexBytes[i]:=0;
   if nrofbytes>4 then nrofbytes:=4;
   for i:=1 to length(inputstr) do
     begin
      write(inputstr[i]);
      if (i<>0) and (i mod 2=0) and (i<(length(inputstr))) then write(' ');
     end;
   charin:=char(0);
   while ((charin<>keymap.esc) and (charin<>keymap.F2) and (charin<>keymap.F3) and (charin<>keymap.F4) and (charin<>keymap.enter)) do
   begin
    charin:=ReadKey();
    if (byte(charin)>=97) and (byte(charin)<=102) then charin:=char(byte(charin)-32);
    if (((byte(charin)>=48) and (byte(charin)<=57)) or ((byte(charin)>=65) and (byte(charin)<=70)))
       and (length(inputstr)<(nrofbytes*2)) then
     begin
       inputstr:=inputstr+charin;
       write(charin);
       if (length(inputstr)>0) and ((length(inputstr) mod 2)=0) and (length(inputstr)<(nrofbytes*2))then write(' ');
     end
    else if (charin=keymap.back) and (Length(inputstr)>0) then
     begin
       if ((length(inputstr) mod 2)=0) and (length(inputstr)<(nrofbytes*2)) then write(keymap.back);
       inputstr:=Copy(inputstr, 1, (Length(inputstr)-1));
       write(keymap.back);
       write('_');
       write(keymap.back);
     end;
   end;
   while (length(inputstr)<(nrofbytes*2)) do
    begin
      inputstr:=inputstr+'0';
      write('0');
      if (length(inputstr)>0) and ((length(inputstr) mod 2)=0) then write(' ');
    end;
   while (length(inputstr)<(4*2)) do
    inputstr:='0'+inputstr;
   for i:=0 to 3 do
    begin
     inputhexbytes[i]:=hexstr2byte(copy(inputstr,1+i*2,2));
    end;
   if ((charin=keymap.esc) or (charin=keymap.F2) or (charin=keymap.F3) or (charin=keymap.F4)) then errormsg:='Canceled'
   else errormsg:='';
end;

// =========================================
//              GENERAL LAYOUT
// =========================================

procedure drawLineV(x,y,len:integer; c:char);
var count:integer;
begin
   for count:=0 to (len-1) do
    begin
      gotoxy(x,y+count);
      write(c);
    end;
end;

procedure drawLineH(x,y,len:integer; c:char);
var count:integer;
begin
  gotoxy(x,y);
  for count:=0 to len-1 do write(c);
end;

procedure clearBlock(x,y,w,h:integer);
var count:integer;
begin
  for count:=0 to h-1 do
   begin
     gotoxy(x,y+count);
     drawLineH(x,y+count,w,' ');
   end;
end;

procedure drawFrame(x,y,w,h,m:integer; active,fill:boolean );
var charset:integer;
begin
  if active then charset:=2
  else charset:=1;
  gotoxy(x,y);
  write(char(framechar[charset,1]));
  gotoxy(x+w-1,y);
  write(char(framechar[charset,2]));
  gotoxy(x,y+h-1);
  write(char(framechar[charset,3]));
  gotoxy(x+w-1,y+h-1);
  write(char(framechar[charset,4]));
  drawLineH(x+1,y,w-2,char(framechar[charset,5]));
  drawLineH(x+1,y+h-1,w-2,char(framechar[charset,5]));
  drawlineV(x,y+1,h-2,char(framechar[charset,6]));
  drawlineV(x+w-1,y+1,h-2,char(framechar[charset,6]));
  if fill then clearBlock(x+1,y+1,w-2,h-2);
  if (m>1) then
   begin
     gotoxy(x+m-1,y);
     write(char(framechar[charset,7]));
     gotoxy(x+m-1,y+h-1);
     write(char(framechar[charset,8]));
     drawLineV(x+m-1,y+1,h-2,char(framechar[charset,6]));
   end;
  resetCursor();
end; {drawFrame}


// =========================================
//             SELECTION DISPLAY
// =========================================

function calcSelectBin(len:integer):integer;
var
 i:integer;
begin
  calcSelectBin:=len;
  for i:=0 to (len) do
   begin
     if (i mod 4 = 0) and (i<>0) then calcSelectBin:=calcSelectBin+1;
     if (i mod 8 = 0) and (i<>0) then calcSelectBin:=calcSelectBin+2;
   end;
end;

procedure drawSelectBin();
var
 i:integer;
begin
  gotoxy(selectBoundaries[4][1]+1,selectBoundaries[4][2]-2);
  for i:=0 to calcSelectBin(listlen[4]) do write(' ');
  gotoxy(selectBoundaries[4][1]+1+calcSelectBin(listlen[4])-calcSelectBin(select[4]+scroll[4]),selectBoundaries[4][2]-2);
  write(char(25));
  gotoxy(selectBoundaries[4][1]+1,selectBoundaries[4][2]);
  for i:=0 to calcSelectBin(listlen[4]) do write(' ');
  gotoxy(selectBoundaries[4][1]+1+calcSelectBin(listlen[4])-calcSelectBin(select[4]+scroll[4]),selectBoundaries[4][2]);
  write(char(24));
  resetCursor();
end;

procedure drawSelect(l:integer);
begin
  drawLineV(selectBoundaries[l][1],selectBoundaries[l][2],maxdisplay[l]+1,' ');
  gotoxy(selectBoundaries[l][1],selectBoundaries[l][2]+select[l]);
  write('[');
  drawLineV(selectBoundaries[l][3],selectBoundaries[l][2],maxdisplay[l]+1,' ');
  gotoxy(selectBoundaries[l][3],selectBoundaries[l][2]+select[l]);
  write(']');
  resetCursor();
end;

procedure drawSelect();
begin
 if binEd and (level=4) then drawSelectBin()
 else drawSelect(level);;
end;

procedure drawSelects();
var i:integer;
begin
  if level<>4 then
    for i:=1 to 3 do drawSelect(i)
  else drawSelect(4);
end;


// =========================================
//                 LIST DISPLAY
// =========================================

procedure MakePrerequisitesList(len,typeindex,unitindex,optindex:integer);
var
    l,i:integer;
    o:boollist;
begin
  l:=len*8-1;
  if l>18 then l:=18;
  o:=readPrerequisites(len,typeindex,unitindex,optindex);
  maxdisplay[4]:=5;
  listlen[4]:=l;
  for i:=0 to listlen[4] do
   begin
     if o[i] then lev4List[i]:='* ' + uppercase(StructureType(i))
     else lev4List[i]:='  ' + lowercase(StructureType(i));
   end;
end;

procedure MakeOwnersList(typeindex,unitindex,optindex:integer);
var
    i:integer;
    o:boollist;
begin
  o:=readOwner(typeindex,unitindex,optindex);
  maxdisplay[4]:=5;
  listlen[4]:=5;
  for i:=0 to listlen[4] do
   begin
     if o[i] then lev4List[i]:='* ' + uppercase(owners[i])
     else lev4List[i]:='  ' + lowercase(owners[i]);
   end;
end;

procedure makeMovementTypesList(typeindex,unitindex,optindex:integer);
var
    i:integer;
    b:byte;
begin
  b:=getBytes(typeindex,unitindex,optindex,2);
  maxdisplay[4]:=5;
  listlen[4]:=5;
  for i:=0 to listlen[4] do
   begin
     if (i=b) then lev4List[i]:=uppercase(MovementTypes[i])
     else lev4List[i]:=lowercase(MovementTypes[i]);
   end;
end;

procedure makeSoundsList(typeindex,unitindex,optindex:integer);
var
    val,i:integer;
begin
  val:=getBytes(typeindex,unitindex,optindex,2);
  maxdisplay[4]:=5;
  listlen[4]:=35;
  for i:=0 to listlen[4] do
   begin
     if val=sounds[i].val then lev4List[i]:=uppercase(sounds[i].txt)
     else lev4List[i]:=lowercase(sounds[i].txt);
   end;
end;

procedure makeCommandsList(typeindex,unitindex,optindex:integer);
var
    val,i:integer;
begin
  val:=getBytes(typeindex,unitindex,optindex,2);
  maxdisplay[4]:=5;
  listlen[4]:=13;
  for i:=0 to listlen[4] do
   begin
     if (i=val) then lev4List[i]:=uppercase(commands[i])
     else lev4List[i]:=lowercase(commands[i]);
   end;
end;

procedure makeFoundationsList(typeindex,unitindex,optindex:integer);
var
    val,i:integer;
begin
  val:=getBytes(typeindex,unitindex,optindex,2);
  maxdisplay[4]:=5;
  listlen[4]:=6;
  for i:=0 to listlen[4] do
   begin
     if (i=val) then lev4List[i]:=' <' + foundations[i] + '>'
     else lev4List[i]:='  ' + foundations[i];
   end;
end;

procedure makeUnitTypeList(typeIndex,unitindex,optindex:integer);
var
    val,i: integer;
begin
  val:=getBytes(typeIndex,unitindex,optindex,2);
  maxdisplay[4]:=5;
  listlen[4]:=unitListLen+1;
  lev4List[0]:='None';
  for i:=0 to listlen[4] do
   begin
     if (i=val) then lev4List[i+1]:=uppercase(UnitType(i))
     else lev4List[i+1]:=lowercase(UnitType(i));
   end;
end;

procedure makeBooleanList(typeindex,unitindex,optindex:integer);
var
    b,i: integer;
begin
  b:=getBytes(typeindex,unitindex,optindex,2);
  maxdisplay[4]:=1;
  listlen[4]:=1;
  for i:=0 to listlen[4] do
   begin
     if (i=b) then lev4List[i]:=uppercase(booleans[i])
     else lev4List[i]:=lowercase(booleans[i]);
   end;
end;

procedure writeBinList(typeindex,unitindex,optindex:integer);
begin
  gotoxy(selectBoundaries[4][1]+1,selectBoundaries[4][2]-1);
  write(getBinString(typeindex,unitindex,optindex,true));
  gotoxy(selectBoundaries[4][1]+11,selectBoundaries[4][2]+2);
  write(getBytesString(typeindex,unitindex,optindex,true));
end;


procedure drawLev4();
var
    i,last,typeindex,unitindex,optindex:integer;
begin
  typeindex:=select[1]+scroll[1];
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  if not binEd then
   begin
    // DATA TYPE CASE - Creates level 4 list for each data type
    case lev3list[optindex].typeid of
      0 : makeBooleanList(typeindex,unitindex,optindex);            //boolean
      1 : ;                                                         //signed byte
      2 : ;                                                         //2-byte
      3 : ;                                                         //4-byte
      4 : makeOwnersList(typeindex,unitindex,optindex);             //owner
      5 : makeCommandsList(typeindex,unitindex,optindex);           //command
      6 : makeMovementTypesList(typeindex,unitindex,optindex);      //Movement type
      7 : makeUnitTypeList(typeindex,unitindex,optindex);           //weapon type
      8 : makeFoundationslist(typeindex,unitindex,optindex);        //foundation
      9 : makeUnitTypeList(typeindex,unitindex,optindex);           //construction option
     10 : makePrerequisitesList(4,typeindex,unitindex,optindex);    //structures
     11 : makeSoundsList(typeindex,unitindex,optindex);             //sounds
     end;
    if listlen[4]<maxdisplay[4] then last:=listlen[4]
    else last:=maxdisplay[4];
    for i:=0 to last do
     begin
       gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]+i);
       write('                   ');
       gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]+i);
       write(lev4List[(i+scroll[4])]);
     end;
   end
  else writeBinList(typeindex,unitindex,optindex);
  drawSelect();
end;

procedure drawLev3data();
var i,last: integer;
    showlist:array [0..19] of String[20];
begin
  if listlen[3]<maxdisplay[3] then last:=listlen[3]
  else last:=maxdisplay[3];
  for i:=0 to last do
  begin
    if not HexDisplay then showlist[i]:=getDataStr(select[1]+scroll[1],select[2]+scroll[2],i+scroll[3])
    else showlist[i]:=getBytesString(select[1]+scroll[1],select[2]+scroll[2],i+scroll[3],true);
  end;
  for i:=0 to last do
  begin
    gotoxy(selectBoundaries[3][1]+27,selectBoundaries[3][2]+i);
    write('                   ');
    gotoxy(selectBoundaries[3][1]+27,selectBoundaries[3][2]+i);
    write(showlist[i]);
  end;
  resetCursor();
end;

procedure drawLev3();
var i,last: integer;
begin
  if listlen[3]<maxdisplay[3] then last:=listlen[3]
  else last:=maxdisplay[3];
  for i:=0 to last do
  begin
    gotoxy(selectBoundaries[3][1]+2,selectBoundaries[3][2]+i);
    write(lev3List[(i+scroll[3])].text);
  end;
  drawLev3data();
end;

procedure drawLev2();
var i,last: integer;
begin
  if listlen[2]<maxdisplay[2] then last:=listlen[2]
  else last:=maxdisplay[2];
  for i:=0 to last do
  begin
    gotoxy(selectBoundaries[2][1]+2,selectBoundaries[2][2]+i);
    write('                   ');
    gotoxy(selectBoundaries[2][1]+2,selectBoundaries[2][2]+i);
    write(lev2List[(i+scroll[2])]);
  end;
  resetCursor();
end;

procedure drawLev1();
var i,last: integer;
begin
  if listlen[1]<maxdisplay[1] then last:=listlen[1]
  else last:=maxdisplay[1];
  for i:=0 to last do
  begin
    gotoxy(selectBoundaries[1][1]+2,selectBoundaries[1][2]+i);
    write(mainlist[(i+scroll[1])]);
  end;
  resetCursor();
end;


procedure drawList(l:integer);
begin
  if l=1 then drawLev1()
  else if l=2 then drawLev2()
  else if l=3 then drawLev3()
  else if l=4 then drawLev4();
end;

procedure drawList();
begin
   drawList(level);
end;

procedure drawLists();
var i:integer;
begin
   for i:=1 to 3 do drawList(i);
end;

procedure updateLev2Lists();
var i:integer;
begin
  if select[1]=0 then
   // LOAD UNITS LIST DATA
   begin
     listlen[3]:=unitOptsListLen;
     lev3List:=unitOptsList;
     listlen[2]:=UnitListLen;
     for i:=0 to listlen[2] do
       lev2List[i]:=getRefString(select[1]+scroll[1],i,1);
   end
  else if select[1]=1 then
   // LOAD STRUCTURES LIST DATA
   begin
     listlen[3]:=structOptsListLen;
     lev3List:=structOptsList;
     listlen[2]:=StructListLen;
     for i:=0 to listlen[2] do
       lev2List[i]:=getRefString(select[1]+scroll[1],i,1);
  end;
end;

procedure updateLists();
var i: integer;
begin
  updateLev2Lists();
  for i:=2 to 3 do
    begin
      scroll[i]:=0;
      select[i]:=0;
      drawSelect(i);
    end;
    drawLists();
end;

// =========================================
//                INPUT LAYOUT
// =========================================

procedure showinputInt(typeindex,unitindex,optindex,minval,maxval:integer);
var
    input:integer;
    curval:String;
begin
   maxdisplay[4]:=0;
   listlen[4]:=0;
   scroll[4]:=0;
   select[4]:=0;
   if (typedescr[lev3list[optindex].typeid][2])<>'' then
        gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-2)
   else gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-3);
   write('Old value : ');
   curval:=toString(getBytes(typeindex,unitindex,optindex,getDataLen(typeindex,optindex)));
   write(curval);
   gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
   write('___________________');
   drawSelect();
   gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
   input:=inputValue(curval,minval,maxval);
   gotoxy(selectBoundaries[4][1]+32,selectBoundaries[4][2]);
   if length(errormsg)=0 then
    begin
      // SAVE
      writeBytesInt(getAddress(typeindex,unitindex,optindex),input,getDataLen(lev3list[optindex].typeid));
    end
   else if errormsg<> 'Canceled' then
    begin
      write(errormsg);
      cntin();
    end;
   resetCursor();
   level:=prevlev;
end;

procedure showinputInt(typeindex,unitindex,optindex:integer;signed:boolean);
var i,minval,maxval:integer;
begin
  minval:=-128;
  maxval:=0;
  for i:=2 to getDataLen(typeindex,optindex) do
    minval:=minval*256;
  if signed then maxval:=(minval+1)*-1
  else
   begin
     maxval:=((minval+1)*-2)+1;
     minval:=0;
   end;
  showinputInt(typeindex,unitindex,optindex,minval,maxval);
end;

procedure showinputInt2(typeindex,unitindex,optindex:integer);
var i,minval,maxval:integer;
begin
  minval:=-128;
  maxval:=0;
  gotoxy(1,2);
  for i:=2 to getDataLen(typeindex,optindex) do
    minval:=minval*256;
  maxval:=(minval+1)*-1;
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-4);
  write('Data range: ');
  write(minval,',',maxval);
  showinputInt(typeindex,unitindex,optindex,minval,maxval);
end;


procedure showinputOffset(typeindex,unitindex,optindex:integer);
var
    input:integer;
    curval:integer;
    ref:fourbyte;
begin
  maxdisplay[4]:=0;
  listlen[4]:=0;
  scroll[4]:=0;
  select[4]:=0;
  if (typedescr[lev3list[optindex].typeid][2])<>'' then
    gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-2)
  else gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-3);
  write('Old value : ');
  curval:=getRefAddress(typeindex,unitindex,optindex);
  write(getBytesString(typeindex,unitindex,optindex,true));
  write(' (');
  if curval<>0 then write('offset '+ toString(curval))
  else write('blank');
  write(')');
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+2);
  write('WARNING - Don''t edit these unless you know');
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+3);
  write('          exactly what you''re doing!');
  gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
  write('___________________');
  drawSelect();
  gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
  input:=inputValue(toString(curval),0,filesize(exefile)-1);
  gotoxy(selectBoundaries[4][1]+32,selectBoundaries[4][2]);
  if length(errormsg)=0 then
   begin
     // SAVE
     ref:=getOffsetRef(input);
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+2);
     write('                                                 ');
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+2);
     write('New value : ');
     write(getBytesString(4,ref,true));
     if input<>0 then begin write(' (offset '); write(getRefAddress(ref)); write(')'); end
     else write(' (blank)');
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+3);
     write('                                                 ');
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+3);
     write('New string: ');
     if input<>0 then write('"');
     write(getRefString(ref));
     if input<>0 then write('"');
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+5);
     write('[ENTER]/[SPACE]: save     [ESC]: cancel');
     keyread:=ReadKey();
     if (keyread=keymap.ENTER) or (keyread=keymap.ENTER) then
       writeBytes(getAddress(typeindex,unitindex,optindex),ref,4);
   end
  else if errormsg<> 'Canceled' then
   begin
     write(errormsg);
     cntin();
   end;
  resetCursor();
  level:=prevlev;
end;

procedure showinputHex(typeindex,unitindex,optindex:integer);
var
    input:fourbyte;
begin
  maxdisplay[4]:=0;
  listlen[4]:=0;
  scroll[4]:=0;
  select[4]:=0;
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-4);
  write('Old value : ' + getBytesString(typeindex,unitindex,optindex,true));
  drawSelect();
  gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
  input:=inputHexBytes(getBytesString(typeindex,unitindex,optindex,false),getDataLen(typeindex,optindex));
  gotoxy(selectBoundaries[4][1]+32,selectBoundaries[4][2]);
  if length(errormsg)=0 then
    writeBytes(getAddress(typeindex,unitindex,optindex),input,getDataLen(typeindex,optindex));
  resetCursor();
  level:=prevlev;
end;


procedure showinputBin(typeindex,unitindex,optindex:integer);
begin
  binEd:=true;
  maxdisplay[4]:=0;
  listlen[4]:=getDataLen(typeindex,optindex)*8-1;
  scroll[4]:=0;
  select[4]:=0;
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-4);
  write('Old value : ' + getBytesString(typeindex,unitindex,optindex,true));
  gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]+1);
  drawSelect();
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+2);
  write('New value:');
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-2);
  writeBinList(typeindex,unitindex,optindex);
  drawSelectBin();
end;


procedure showBasicList(typeindex,unitindex,optindex:integer);
var
    val:integer;
begin
  val:=readBytesInt(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex));
  if getDataStr(typeindex,unitindex,optindex) <> '<Invalid type>' then moveDown(val);
  drawLev4();
end;

procedure showSoundsList(typeindex,unitindex,optindex:integer);
var
    scroll,i,val:byte;
begin
  val:=readBytesInt(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex));
  for i:=0 to 35 do
   if val=sounds[i].val then scroll:=i;
  if getDataStr(typeindex,unitindex,optindex) <> '<Unknown>' then moveDown(scroll);
  drawLev4();
end;

procedure showUnitTypeList(typeindex,unitindex,optindex:integer);
var
    i:integer;
begin
  i:=1+getBytes(typeindex,unitindex,optindex,2);
  if i<>-1 then moveDown(i);
  drawLev4();
end;

// =========================================
//                MAIN LAYOUT
// =========================================

procedure drawLevFrame(lvl:integer);
begin
  case lvl of
   1 : drawFrame(1,3,27,4,-1,level=1,false);
   2 : drawFrame(1,8,27,17,-1,level=2,false);
   3 : drawFrame(30,3,51,22,27,level=3,false);
   4 : drawFrame(15,5,52,15,-1,true,true);
  end;
end;

procedure drawLevFrame();
begin
  drawLevFrame(level);
end;

procedure drawLevFrames();
var i: integer;
begin
  for i:=1 to 3 do
   begin
     drawLevFrame(i);
   end;
end;


procedure clearWorkArea();
var i: integer;
begin
  for i:=3 to 24 do
   begin
     gotoxy(1,i);
     clreol();
   end;
end;

procedure drawLayout4(edtype:integer);
var
    typeindex,unitindex,optindex:integer;
begin
  typeindex:=select[1]+scroll[1];
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  drawLevFrame(prevlev);
  drawLevFrame(4);
  gotoxy(selectBoundaries[4][1]+1,selectBoundaries[4][2]-7);
  write('[');
  if edtype=1 then write('BIN ');
  if edtype=2 then write('HEX ');
  if edtype=3 then write('DEC ');
  write('EDIT]');
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-6);
  write('Section   : ');
  write(lev2list[unitindex]);
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-5);
  write('Option    : ');
  write(lev3list[optindex].text);
  if (edtype=0) then
   begin
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-4);
     write('Data type : ');
     gotoxy(selectBoundaries[4][1]+12,selectBoundaries[4][2]-4);
     write(typedescr[lev3list[optindex].typeid][1]);
     gotoxy(selectBoundaries[4][1]+12,selectBoundaries[4][2]-3);
     write(typedescr[lev3list[optindex].typeid][2]);
     gotoxy(17,10);
     maxdisplay[4]:=0;
     listlen[4]:=0;
     select[4]:=0;
     scroll[4]:=0;
     drawList();
     // DATA TYPE CASE - Determines the way each data type is edited
     case lev3list[optindex].typeid of
       0 : showBasicList(typeindex,unitindex,optindex);         //boolean
       1 : showinputInt(typeindex,unitindex,optindex,true);     //signed byte
       2 : showinputInt(typeindex,unitindex,optindex,true);     //2-byte
       3 : showinputOffset(typeindex,unitindex,optindex);       //4-byte
       4 : ; {no special show instructions needed}              //owner
       5 : showBasicList(typeindex,unitindex,optindex);         //command
       6 : showBasicList(typeindex,unitindex,optindex);         //Movement type
       7 : showUnitTypeList(typeindex,unitindex,optindex);      //weapon type
       8 : showBasicList(typeindex,unitindex,optindex);         //foundation
       9 : showUnitTypeList(typeindex,unitindex,optindex);      //construction option
      10 : ; {no special show instructions needed}              //structures
      11 : showSoundsList(typeindex,unitindex,optindex);        //sounds
     end;
   end
  else if edtype=1 then showinputBin(typeindex,unitindex,optindex)
  else if edtype=2 then showinputHex(typeindex,unitindex,optindex)
  else if edtype=3 then showinputInt2(typeindex,unitindex,optindex);
  if level<>4 then
   begin
     ClearWorkArea();
     drawLevFrames();
     drawLists();
     drawSelects();
   end;
end;

procedure drawLayout();
begin
   if level=4 then drawLayout4(0)
   else drawLevFrames();
end;

procedure FillWorkArea();
begin
      ClearWorkArea();
      drawLayout();
      drawLists();
      drawSelects();
end;

// =========================================
//              LEVEL 4 CONTROLS
// =========================================

procedure startlev4();
begin
  prevlev:=level;
  level:=4;
  if not HexDisplay then drawLayout()
  else drawLayout4(2);
end;

procedure startlev4bin();
begin
  prevlev:=level;
  level:=4;
  drawLayout4(1);
end;

procedure startlev4hex();
begin
  prevlev:=level;
  level:=4;
  drawLayout4(2);
end;

procedure startlev4dec();
begin
  prevlev:=level;
  level:=4;
  drawLayout4(3);
end;

procedure exitlev4();
begin
  select[4]:=0;
  scroll[4]:=0;
  level:=prevlev;
  binEd:=false;
  FillWorkArea();
end;

// =========================================
//              OUTPUT PROCEDURES
// =========================================

procedure savePrerequisites(len,typeindex,unitindex,optindex:integer);
var
 o:boollist;
 listpos:integer;
begin
  if keyread=keymap.SPACE then
   begin
     o:=readPrerequisites(len,typeindex,unitindex,optindex);
     listpos:=select[4]+scroll[4];
     o[listpos]:=(not o[listpos]);
     writePrerequisites(len,o,typeindex,unitindex,optindex);
     drawLev4();
   end
  else if keyread=keymap.ENTER then exitlev4()
end;

procedure saveOwners(typeindex,unitindex,optindex:integer);
var
 o:boollist;
 listpos:integer;
begin
  if keyread=keymap.SPACE then
   begin
     o:=readOwner(typeindex,unitindex,optindex);
     listpos:=select[4]+scroll[4];
     o[listpos]:=(not o[listpos]);
     writeOwners(o,typeindex,unitindex,optindex);
     drawLev4();
   end
  else if keyread=keymap.ENTER then exitlev4()
end;


procedure saveBasicList(typeindex,unitindex,optindex:integer;hasblank:boolean);
var
  value:integer;
begin
  value:=select[4]+scroll[4];
  if hasblank then value:=value-1;
  writeBytesInt(getAddress(typeindex,unitindex,optindex),value,getDataLen(lev3list[optindex].typeid));
  exitlev4();
end;

procedure saveSoundsList(typeindex,unitindex,optindex:integer);
var
  value:integer;
begin
  value:=sounds[(select[4]+scroll[4])].val;
  writeBytesInt(getAddress(typeindex,unitindex,optindex),value,getDataLen(lev3list[optindex].typeid));
  exitlev4();
end;


procedure inputLev4();
var
    typeindex,unitindex,optindex:integer;
begin
  typeindex:=select[1]+scroll[1];
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  if binEd then savePrerequisites(getDataLen(typeindex,optindex),typeindex,unitindex,optindex)
  else
  // DATA TYPE CASE - Determines which procedure to call to save each data type
   case lev3list[select[3]+scroll[3]].typeid of
     0 : saveBasicList(typeindex,unitindex,optindex,false);      //Boolean
     1 : ;                                                       //signed byte
     2 : ;                                                       //2-byte
     3 : ;                                                       //4-byte
     4 : saveOwners(typeindex,unitindex,optindex);               //owner
     5 : saveBasicList(typeindex,unitindex,optindex,false);      //command
     6 : saveBasicList(typeindex,unitindex,optindex,false);      //Movement type
     7 : saveBasicList(typeindex,unitindex,optindex,true);       //weapon type
     8 : saveBasicList(typeindex,unitindex,optindex,false);      //foundation
     9 : saveBasicList(typeindex,unitindex,optindex,true);       //construction option
    10 : savePrerequisites(4,typeindex,unitindex,optindex);      //structures
    11 : saveSoundsList(typeindex,unitindex,optindex);           //sounds
   end;
end;

// =========================================
//                HRLP SCREENS
// =========================================

procedure egotrip();
var
   x,y,i:integer;
begin
  x:=selectBoundaries[4][1];
  y:=selectBoundaries[4][2]-7;
  drawFrame(15,5,52,16,-1,true,true);
  gotoxy(x,y); write('[INFO]');
  y:=y+1; gotoxy(x,y); write(editorname + ' ' + editorversion);
  gotoxy(x+31,y); write('Powered by Pascal');
  y:=y+1; gotoxy(x,y); for i:=1 to length(editorname+editorversion)+1 do write(char(framechar[1][5]));
                       //    '________________________________________________'
  y:=y+1; gotoxy(x,y); write('Created by Nyerguds <Nyerguds@gmail.com> in     ');
  y:=y+1; gotoxy(x,y); write('March 2008.');

  y:=y+2; gotoxy(x,y); write('Originally requested by MrFlibble, to help him');
  y:=y+1; gotoxy(x,y); write('in his efforts to create the "Super Dune II');
  y:=y+1; gotoxy(x,y); write('Second Edition" mod.');
  y:=y+1; gotoxy(x,y); write('Many thanks to MrFlibble for providing me with');
  y:=y+1; gotoxy(x,y); write('a description of the data structures in Dune II.');

  y:=y+2; gotoxy(x,y); write('Layout based on the "C&C structure editor V1.02"');
  y:=y+1; gotoxy(x,y); write('by Wladimir van der Laan.');

  resetCursor();
  cntin();
  FillWorkArea();
end;

procedure help();
var
   x,y,i:integer;
begin
  x:=selectBoundaries[4][1];
  y:=selectBoundaries[4][2]-7;
  if level=3 then drawFrame(15,5,52,12,-1,true,true)
  else drawFrame(15,5,52,18,-1,true,true);
  gotoxy(x,y); write('[HELP]');
  y:=y+1; gotoxy(x,y); write(editorname + ' ' + editorversion);
  y:=y+1; gotoxy(x,y); for i:=1 to length(editorname+editorversion)+1 do write(char(framechar[1][5]));
  if level=3 then
   for i:=1 to 7 do
    begin
      y:=y+1;
      gotoxy(x,y);
      write(typehelp[lev3List[select[3]+scroll[3]].typeid][i]);
    end
  else
   begin
     y:=y+1; gotoxy(x,y); write('LEFT/RIGHT');
             gotoxy(x+11,y); write(': Navigate between sections');
     y:=y+1; gotoxy(x,y); write('UP/DOWN');
             gotoxy(x+11,y); write(': Navigate through section list');
     y:=y+1; gotoxy(x+14,y); write('Use PGUP/PGDWN/HOME/END for');
     y:=y+1; gotoxy(x+14,y); write('faster scrolling.');

     y:=y+2; gotoxy(x,y); write('SPACE/ENTER');
             gotoxy(x+11,y); write(': Modify/confirm selected value');
     y:=y+1; gotoxy(x,y); write('F1');
             gotoxy(x+11,y); write(': Help on data type of selected value');
     y:=y+1; gotoxy(x,y); write('F2');
             gotoxy(x+11,y); write(': Edit as bare binary value');
     y:=y+1; gotoxy(x,y); write('F3');
             gotoxy(x+11,y); write(': Edit as bare hexadecimal value');
     y:=y+1; gotoxy(x,y); write('F4');
             gotoxy(x+11,y); write(': Edit as bare decimal value');
     y:=y+2; gotoxy(x,y); write('F11');
             gotoxy(x+11,y); write(': Toggles full hexadecimal mode');
     y:=y+1; gotoxy(x,y); write('F12');
             gotoxy(x+11,y); write(': Info screen about the editor');
   end;
  resetCursor();
  cntin();
  FillWorkArea();
end;


// =========================================
//                NAVIGATION
// =========================================

procedure MoveDown2(lines:integer);
var scr,sel:integer;
begin
  scr:=scroll[level];
  sel:=select[level];
  if not binEd then MoveDown(lines)
  else MoveUp(lines);
  if scr<>scroll[level] then drawList();
  if sel<>select[level] then drawSelect();
  if level=1 then begin updateLists(); end;
  if level=2 then drawLev3data();
end;

procedure MoveUp2(lines:integer);
var scr,sel:integer;
begin
  scr:=scroll[level];
  sel:=select[level];
  if not binEd then MoveUp(lines)
  else MoveDown(lines);
  if scr<>scroll[level] then drawList();
  if sel<>select[level] then drawSelect();
  if level=1 then begin updateLists(); end;
  if level=2 then drawLev3data();
end;

procedure MoveRight2();
begin
  MoveRight();
  drawLayout();
end;

procedure MoveLeft2();
begin
  MoveLeft();
  drawLayout();
end;

procedure SwitchDisplay();
begin
  HexDisplay:=not HexDisplay;
  drawLev3data();
end;

procedure input();
begin
  keyread:=ReadKey();
  if (keyread=keymap.ESC) and (level<>4) then keyread:=char(255)
  else if (level=4) and ((keyread=keymap.ESC) or (keyread=keymap.F2) or (keyread=keymap.F3) or (keyread=keymap.F4)) then exitlev4()
  else if ((level=2) or (level=3)) and ((keyread=keymap.ENTER) or (keyread=keymap.SPACE)) then startlev4()
  else if ((level=2) or (level=3)) and (keyread=keymap.F2) then startlev4bin()
  else if ((level=2) or (level=3)) and (keyread=keymap.F3) then startlev4hex()
  else if ((level=2) or (level=3)) and (keyread=keymap.F4) then startlev4dec()
  else if (keyread=keymap.F11)   and (level<>4) then SwitchDisplay()
  else if (keyread=keymap.RIGHT) and (level<>4) then MoveRight2()
  else if (keyread=keymap.RIGHT) and (level=4) then MoveDown2(1)
  else if (keyread=keymap.LEFT)  and (level<>4) then MoveLeft2()
  else if (keyread=keymap.LEFT)  and (level=4) then MoveUp2(1)
  else if (keyread=keymap.DOWN)  then MoveDown2(1)
  else if (keyread=keymap.UP)    then MoveUp2(1)
  else if (keyread=keymap.PGDN)  then MoveDown2(maxdisplay[level])
  else if (keyread=keymap.PGUP)  then MoveUp2(maxdisplay[level])
  else if (keyread=keymap.ENDK)  then MoveDown2(listlen[level])
  else if (keyread=keymap.HOME)  then MoveUp2(listlen[level])
  else if (keyread=keymap.F1)    and (level<>4) then help()
  else if (keyread=keymap.F12)   and (level<>4) then egotrip()
  else if (level=4) and ((keyread=keymap.SPACE) or (keyread=keymap.ENTER)) then inputlev4()
  else keyread:=char(0);
end;

// =========================================
//               INITIALIZATION
// =========================================


procedure GetListLengths();
var i: integer;
begin
  unitOptsListLen:=-1;
  for i:=0 to 56 do
    if unitOptsList[i].typeid<>-1 then unitOptsListLen:=unitOptsListLen+1
    else break;
  structOptsListLen:=-1;
  for i:=0 to 56 do
    if structOptsList[i].typeid<>-1 then structOptsListLen:=structOptsListLen+1
    else break;
end;


procedure init();
var i: integer;
begin
  for i:=2 to 25 do
  begin
    gotoxy(1,i);
    clreol;
  end;
  gotoxy(1,25);
  write('[F1]=HELP  [F2][F3][F4]=EDIT  [F11]=HEX MODE  [F12]=INFO');
  gotoxy(59,25);
  write(gameverstr + ' ' + dunever[gamever]);
  gotoxy(40,2);
  level:=1;
  GetListLengths();
  listlen[1]:=mainListlen;
  listlen[2]:=unitListLen;
  listlen[3]:=unitOptsListLen;
  listlen[4]:=0;
  HexDisplay:=false;
  for i:=1 to 4 do
  begin
   select[i]:=0;
   scroll[i]:=0;
   maxdisplay[i]:=selectBoundaries[i][4]-selectBoundaries[i][2];
  end;
  updateLev2Lists();
  FillWorkArea();
end;

procedure checkver();
var i: integer;
begin
  gamever:=-1;
  i:=0;
  while ((gamever=-1) and (i<=high(verAddress))) do
   begin
     if (readString(verAddress[i]) = gamenamestr) then gamever:=i;
     i:=i+1;
   end;
end;



begin
  clrscr();
  gotoxy(1,1);
  drawLineH(1,1,80,char(framechar[2,5]));
  gotoxy(3,1);
  write(' '); write(editorname); write(' '); write(editorversion); write(' ');
  gotoxy(65,1);
  write(' by '); write(editorauthor); write(' ');
  gotoxy(1,3);
  if not (openfile(gamenamestr)) then cntin(true)
  else
   begin
     checkver();
     if gamever<>-1 then
      begin
        init();
        keyread:=char(0);
        while (keyread <> char(255)) do
         begin
           input();
         end;
        closeFile();
        clearWorkArea();
        gotoxy(1,25);
        clreol();
        gotoxy(1,3);
        writeln('Program exited. Have fun!');
      end
     else
      begin
       gotoxy(1,3);
       writeln('ERROR: Cannot identify the version of the "DUNE2.EXE" file.');
       cntin(true);
      end;
   end;
end.

