// Program written by Maarten Meuris AKA Nyerguds
program dune_edit;
uses crt, dos;

TYPE
   chset      = array [1..8] of byte;
   coords     = array [1..4] of integer;
   boollist   = array [0..32] of boolean;
   fourbyte   = array [0..3] of byte;
   descr      = array [1..2] of string[36];
   bigdescr   = array [1..7] of string[48];

   keymapper = Record
      up, down, left, right, space, enter, esc, back, pgup, pgdn, home, endk, f1, f3, f12 :char;
   end;
   option = Record
      typeid: Integer;
      text: String[20];
   end;
    lev2Lst = array[0..30] of String;
    lev3Lst = array[0..56] of option;

var
    gamever,level: integer;
    exefile:file;
    keyread:char;
    errormsg:String;
    select,scroll,maxdisplay,listlen:array [1..4] of integer;
    lev2List,lev4List:lev2Lst;
    lev3List:lev3Lst;
const
    editorname:string = 'Dune II Editor';
    editorversion:string = 'v1.4';
    editorauthor:string = 'Nyerguds';
    gameverstr:String = 'EXE version:';
    dunever:array[0..1] of string = ('1.00','1.07');
    refAddress:array[0..1] of fourbyte = (($33, $2B, $81, $10),
                                          ($32, $EC, $85, $00));
    framechar:array[1..2] of chset = (
                       //  |'  '|   |_  _|     -    |   '|'  _|_
                         ($DA, $BF, $C0, $D9, $C4, $B3, $C2, $C1),
                         ($C9, $BB, $C8, $BC, $CD, $BA, $CB, $CA));
    selectBoundaries:array[1..4] of coords = (
                         (3,4,25,5), (3,9,25,23), (32,4,54,23), (17,12,38,18));
    typedescr:array[0..10] 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. '));
    typehelp:array[0..10] of bigdescr = (
     // '                                                '
       ('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.','','','',''));



    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');
    booleans:array[0..1] of string[3] = ('No','Yes');

    mainlistlen:integer=1;
    mainlist:array[0..1] of String[20] = ('Units       ','Structures  ');

    unitlistlen:integer=26;
    unitOptsOffset:array[0..1] of integer= (198480,195760);
    unitOptsListlen:integer=45;
    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: 'Unknown 008         '),
              (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: 'Sight               '),
              (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: 2; text: 'Unknown 046         '),
              (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: ''));

    structlistlen:integer=18;
    structOptsOffset:array[0..1] of integer= (196570,193930);
    structOptsListlen:integer=53;
    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: 'Unknown 008         '),
              (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: ''));
      keymap:keymapper = (up:    char(72);
                          down:  char(80);
                          left:  char(75);
                          right: char(77);
                          space: char(32);
                          enter: char(13);
                          esc:   char(27);
                          back:  char(8);
                          pgup:  char(73);
                          pgdn:  char(81);
                          home:  char(71);
                          endk:  char(79);
                          f1:    char(59);
                          f3:    char(61);
                          f12:   char(134));


// =========================================
//                 MISCELLANEOUS
// =========================================

procedure cntin;
var pause: char;
begin
   repeat until keypressed;
    pause:=ReadKey();
end; {cntin}

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;

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;

function toString(b:byte):String;
begin
  str(b,toString);
end;

function toString(i:Integer):String;
begin
  str(i,toString);
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;

// =========================================
//              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;

function read2Byte(address:Integer):integer;
begin
  read2Byte:=readByte(address)+ 256*readByte(address+1);
end;

procedure write2Byte(address,data:Integer);
var
  actual:word;
  buffer:array[1..2] of byte;
begin
  if data<0 then data:=data+65536;
  buffer[1]:=byte(data mod 256);
  buffer[2]:=byte(data div 256);
  seek(exefile,address);
  blockwrite(exefile,buffer,2,actual);
end;

procedure writeByte(address:Integer; data:byte);
var
  actual:word;
begin
  seek(exefile,address);
  blockwrite(exefile,data,1,actual);
end;

function readHex4(address:Integer):fourbyte;
var i:integer;
begin
  for i:=0 to 3 do
    readHex4[i]:=readByte(address+3-i);
end;

procedure writeBytes(len:integer;inputhex:fourbyte;address:Integer);
var i:integer;
begin
  for i:=0 to len-1 do
    writeByte(address+len-1-i,inputhex[i]);
end;

// =========================================
//              DATA & OFFSETS
// =========================================

function getDataLen(opttype:integer):integer;
begin
  case opttype of
   0 : getDataLen:=2;  //Boolean (2 byte)
   1 : getDataLen:=1;  //Signed byte (dec)
   2 : getDataLen:=2;  //2-byte
   3 : getDataLen:=4;  //4-byte (HEX)
   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(full)
   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;

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;

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;

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;

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(readHex4(getAddress(typeindex,unitindex,optindex)));
end;


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(readHex4(getAddress(typeindex,unitindex,optindex)));
end;

function getBytesString(bytes:integer;inputhex:fourbyte;split:boolean):string;
var last,i:integer;
begin
  getBytesString:='';
  if bytes<=4 then last:=4-bytes
  else last:=3;
  for i:=last 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),readHex4(getAddress(typeindex,unitindex,optindex)),split);
end;

function get2Byte(typeindex,unitindex,optindex:integer;signed:boolean):integer;
begin
  get2Byte:=read2Byte(getAddress(typeindex,unitindex,optindex));
  if signed and (get2Byte>$7FFF) then get2Byte:=get2Byte-$10000;
end;

function getByte(typeindex,unitindex,optindex:integer;signed:boolean):integer;
begin
  getByte:=readByte(getAddress(typeindex,unitindex,optindex));
  if signed and (getByte>$7F) then getByte:=getByte-$100;
end;

function getMovementTypestr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=get2Byte(typeindex,unitindex,optindex,false);
  if (i>=0) and (i<=5) then
    getMovementTypestr:=movementTypes[i]
  else getMovementTypestr:='<Invalid type>';
end;

function getCommandstr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=get2Byte(typeindex,unitindex,optindex,false);
  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:=get2Byte(typeindex,unitindex,optindex,false);
  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:=get2Byte(typeindex,unitindex,optindex,true);
  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:=readByte(getAddress(typeindex,unitindex,optindex)+i);
     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;


function getDataStr(typeindex,unitindex,optindex:integer):string;
begin
  getDataStr:='';
  case lev3list[optindex].typeid of
   0 : getDataStr:=getBooleanstr(typeindex,unitindex,optindex);                 //boolean
   1 : getDataStr:=tostring(getByte(typeindex,unitindex,optindex,true));        //signed byte
   2 : getDataStr:=toString(get2Byte(typeindex,unitindex,optindex,true));       //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

   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.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) 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;bytes:integer):fourbyte;
var charin: char;
    i:integer;
begin
   if bytes>4 then bytes:=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.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)<(bytes*2)) then
     begin
       inputstr:=inputstr+charin;
       write(charin);
       if (length(inputstr)>0) and ((length(inputstr) mod 2)=0) and (length(inputstr)<(bytes*2))then write(' ');
     end
    else if (charin=keymap.back) and (Length(inputstr)>0) then
     begin
       if ((length(inputstr) mod 2)=0) and (length(inputstr)<(bytes*2)) then write(keymap.back);
       inputstr:=Copy(inputstr, 1, (Length(inputstr)-1));
       write(keymap.back);
       write('_');
       write(keymap.back);
     end;
   end;
   while (length(inputstr)<(bytes*2)) do
   begin
       inputstr:=inputstr+'0';
       write('0');
       if (length(inputstr)>0) and ((length(inputstr) mod 2)=0) then write(' ');
   end;
   for i:=0 to 3 do
    begin
     inputhexbytes[i]:=hexstr2byte(copy(inputstr,1+i*2,2));
    end;
   if (charin=keymap.esc) 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:=1 to len do write(c);
end;

procedure drawFrame(x,y,w,h,m:integer; active,fill:boolean );
var charset,count,count2:integer;
begin
   if active then charset:=2
   else charset:=1;
   gotoxy(x,y);
   write(char(framechar[charset,1]));
   for count:=1 to (w-2) do
    write(char(framechar[charset,5]));
   write(char(framechar[charset,2]));
   for count:=1 to (h-2) do
    begin
      gotoxy(x,y+count);
      write(char(framechar[charset,6]));
      if fill then for count2:=1 to (w-2) do write(' ');
      gotoxy(x+w-1,y+count);
      write(char(framechar[charset,6]));
    end;
   gotoxy(x,y+h-1);
   write(char(framechar[charset,3]));
   for count:=1 to (w-2) do
    write(char(framechar[charset,5]));
   write(char(framechar[charset,4]));

   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;
   gotoxy(0,0);
end; {drawFrame}


// =========================================
//             SELECTION DISPLAY
// =========================================

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
  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:=readByte(getAddress(typeindex,unitindex,optindex));
  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 makeCommandsList(typeindex,unitindex,optindex:integer);
var
    val,i:integer;
begin
  val:=readByte(getAddress(typeindex,unitindex,optindex));
  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:=get2Byte(typeindex,unitindex,optindex,true);
  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:=get2Byte(typeIndex,unitindex,optindex,true);
  maxdisplay[4]:=5;
  listlen[4]:=unitListLen+1;
  resetCursor();
  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:=readByte(getAddress(typeIndex,unitindex,optindex));
  maxdisplay[4]:=1;
  listlen[4]:=1;
  resetCursor();
  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 drawLev4();
var
    i,last,typeindex,unitindex,optindex:integer;
begin
  typeindex:=select[1]+scroll[1];
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  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(full)
   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;
  drawSelect();
  resetCursor();
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
    showlist[i]:=getDataStr(select[1]+scroll[1],select[2]+scroll[2],i+scroll[3]);
  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 updateLists();
var i: integer;
begin
  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
    size,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:=getDataStr(typeindex,unitindex,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
      size:=getDataLen(lev3list[optindex].typeid);
      if size=1      then writeByte(getAddress(typeindex,unitindex,optindex),input)
      else if size=2 then write2Byte(getAddress(typeindex,unitindex,optindex),input);
    end
   else if errormsg<> 'Canceled' then
    begin
      write(errormsg);
      cntin();
    end;
   resetCursor();
   level:=3;
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');
      resetCursor();
      keyread:=ReadKey();
      if (keyread=keymap.ENTER) or (keyread=keymap.ENTER) then
        writeBytes(4,ref,getAddress(typeindex,unitindex,optindex));
    end
   else if errormsg<> 'Canceled' then
    begin
      write(errormsg);
      cntin();
    end;
   resetCursor();
   level:=3;
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]-2);
   write('Old value : ' + getBytesString(typeindex,unitindex,optindex,true));
   gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
   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(getDataLen(typeindex,optindex),input,getAddress(typeindex,unitindex,optindex));
   resetCursor();
   level:=3;
end;


procedure showBasicList(typeindex,unitindex,optindex:integer);
var
    b:byte;
begin
  b:=readByte(getAddress(typeindex,unitindex,optindex));
  if getDataStr(typeindex,unitindex,optindex) <> '<Invalid type>' then moveDown(b);
  drawLev4();
end;

procedure showUnitTypeList(typeindex,unitindex,optindex:integer);
var
    i:integer;
begin
  i:=1+get2Byte(typeindex,unitindex,optindex,true);
  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 clearWorkArea();
var i: integer;
begin
    for i:=3 to 24 do
      begin
        gotoxy(1,i);
        clreol();
      end;
end;

procedure drawLayout4(normal:boolean);
var
    typeindex,unitindex,optindex:integer;
begin
  typeindex:=select[1]+scroll[1];
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  drawLevFrame(3);
  drawLevFrame(4);
  gotoxy(selectBoundaries[4][1]+1,selectBoundaries[4][2]-7);
  write('[');
  if not normal then write('HEX ');
  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);
   gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-4);
  if normal then
   begin
     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();
     case lev3list[optindex].typeid of
       0 : showBasicList(typeindex,unitindex,optindex);                         //boolean
       1 : showinputInt(typeindex,unitindex,optindex,-128,127);                 //signed byte
       2 : showinputInt(typeindex,unitindex,optindex,-32768,32767);             //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(full)
     end;
   end
  else showinputHex(typeindex,unitindex,optindex);
  if level<>4 then
   begin
     clearWorkArea();
     drawLevFrame(1);
     drawLevFrame(2);
     drawLevFrame(3);
     drawSelect();
     drawLists();
     drawSelects();
   end
end;

procedure drawLayout();
begin
   if level=4 then drawLayout4(true)
   else
    begin
      drawLevFrame(1);
      drawLevFrame(2);
      drawLevFrame(3);
      drawSelect();
    end;
end;

procedure FillWorkArea();
begin
      ClearWorkArea();
      drawLayout();
      drawLists();
      drawSelects();
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;
// =========================================
//              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
   begin
     level:=3;
     FillWorkArea();
   end;
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
   begin
     level:=3;
     FillWorkArea();
   end;
end;


procedure saveBasicList(typeindex,unitindex,optindex:integer;hasblank:boolean);
var
 size,listpos:integer;
begin
  listpos:=select[4]+scroll[4];
  if hasblank then listpos:=listpos-1;
  size:=getDataLen(lev3list[optindex].typeid);
  if size=1      then writeByte(getAddress(typeindex,unitindex,optindex),listpos)
  else if size=2 then write2Byte(getAddress(typeindex,unitindex,optindex),listpos);
  level:=3;
  FillWorkArea();
end;

procedure inputLev4();
var
    typeindex,unitindex,optindex:integer;
begin
  typeindex:=select[1]+scroll[1];
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  case lev3list[select[3]+scroll[3]].typeid of
    0 : saveBasicList(typeindex,unitindex,optindex,false);      //signed byte
    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(full)
   end;
end;

procedure exitlev4();
begin
  select[4]:=0;
  scroll[4]:=0;
  level:=3;
  FillWorkArea();
end;

procedure startlev4();
begin
  FillWorkArea();
  level:=4;
  drawLayout();
end;

procedure startlev4hex();
begin
  FillWorkArea();
  level:=4;
  drawLayout4(false);
end;


// =========================================
//                HRLP SCREENS
// =========================================

procedure egotrip();
var
   x,y,i:integer;
begin
  x:=selectBoundaries[4][1];
  y:=selectBoundaries[4][2]-7;
  drawLevFrame(4);
  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+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;
  drawLevFrame(4);
  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  : Navigate between sections');
     y:=y+2; gotoxy(x,y); write('UP/DOWN, HOME/END, PGUP/PGDWN');
     y:=y+1; gotoxy(x+12,y); write(': Navigate through section list');
     y:=y+2; gotoxy(x,y); write('ENTER/SPACE : Modify selected value');
     y:=y+1; gotoxy(x,y); write('F1          : Help about selected data type');
     y:=y+1; gotoxy(x,y); write('F3          : Edit as bare hexadecimal value');
     y:=y+2; gotoxy(x,y); write('F12         : Info screen about the editor');
   end;
  resetCursor();
  cntin();
  FillWorkArea();
end;


// =========================================
//                NAVIGATION
// =========================================

procedure MoveDown2(lines:integer);
begin
  MoveDown(lines);
  drawList();
  drawSelects();
  if level=1 then begin updateLev2Lists(); updateLists(); end;
  if level=2 then drawLev3data();
end;

procedure MoveUp2(lines:integer);
begin
  MoveUp(lines);
  drawList();
  drawSelects();
  if level=1 then begin updateLev2Lists(); updateLists(); end;
  if level=2 then drawLev3data();
end;

procedure MoveRight2();
begin
  MoveRight();
  FillWorkArea();
end;

procedure MoveLeft2();
begin
  MoveLeft();
  FillWorkArea();
end;

procedure input();
begin
  if (keyread=keymap.ESC) and (level<>4) then keyread:=char(255)
  else if (level=3) and ((keyread=keymap.ENTER) or (keyread=keymap.SPACE)) then startlev4()
  else if (level=3) and (keyread=keymap.F3) then startlev4hex()
  else if (keyread=keymap.RIGHT) and (level<>4) then MoveRight2()
  else if (keyread=keymap.LEFT)  and (level<>4) then MoveLeft2()
  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.ESC) then exitlev4()
  else if (level=4) and ((keyread=keymap.SPACE) or (keyread=keymap.ENTER)) then inputlev4()
  else keyread:=char(0);

end;

// =========================================
//               INITIALIZATION
// =========================================

procedure init();
var i: integer;
begin
  for i:=2 to 25 do
  begin
    gotoxy(1,i);
    clreol;
  end;
  gotoxy(1,25);
  write('[F1]: HELP   [F3]: HEX EDIT   [F12]: INFO');
  gotoxy(60,25);
  write(' ' + gameverstr + ' ' + dunever[gamever]);
  gotoxy(40,2);
  level:=1;
  listlen[1]:=mainListlen;
  listlen[2]:=unitListLen;
  listlen[3]:=unitOptsListLen;
  listlen[4]:=0;
  for i:=1 to 4 do
  begin
   select[i]:=0;
   scroll[i]:=0;
   maxdisplay[i]:=selectBoundaries[i][4]-selectBoundaries[i][2];
   drawSelect(i);
  end;
  updateLev2Lists();
  drawLists();
end;



begin
  gotoxy(1,1);
  clreol();
  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);
  gamever:=-1;
  if not (openfile('DUNE2.EXE')) then ReadKey()
  else
   begin
     if (readString(228274) = 'DUNE2.EXE') then gamever:=1           //1.07
     else if (readString(229282) = 'DUNE2.EXE') then gamever:=0;     //1.00
     if gamever<>-1 then
      begin
        init();
        drawLayout();
        keyread:=char(0);
        while keyread <> char(255) do
         begin
           keyread:=ReadKey();
           resetCursor();
           input();
         end;
        closeFile();
      end
     else
      begin
       gotoxy(1,3);
       writeln('Invalid "DUNE2.EXE" file.');
       writeln('This program only works on the 1.07 version of Dune II.');
       cntin();
      end;
   end;
end.

