// 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;
   ownership = array [1..6] of boolean;
   fourbyte  = array [0..3] of byte;
   descr     = array [1..2] of string[36];

   keymapper = Record
      up, down, left, right, space, enter, esc, back, pgup, pgdn, home, endk :char;
   end;
   option = Record
      typeid: Integer;
      text: String[20];
   end;
    lev2Lst = array[0..27] of String;
    lev3Lst = array[0..56] of option;

var
    optsOffset,level: integer;
    exefile:file;
    keyread:char;
    errormsg:String;
    select,scroll,maxdisplay,listlen:array [1..4] of integer;
    lev2List:lev2Lst;
    lev3List:lev3Lst;
const
    refAddress:array[0..3] of byte = ($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));
    typeids:array[1..10] of descr = (
     // '                                   ' '                                   '
       ('Single byte (0,255)                ','                                   '),
       ('Double byte (-32768,32767)         ','                                   '),
       ('Reference to 0-terminated string.  ','Give an 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.                              '),
       ('Prerquisite structures. Press space','to select or deselect a structure. '),
       ('Construction option. Select one    ','unit from the list.'),
       ('Build grid. Select a build grid    ','from the list.')
                                     );
    owners:array[1..6] of String[9] = ('Harkonnen','Atreides','Ordos',
                                       'Fremen','Sardaukar','Mercenary');


    mainlistlen:integer=1;
    mainlist:array[0..1] of String[20] = ('Units       ','Structures  ');

    unitlistlen:integer=26;
    unitOptsOffset:integer=195760;
    unitOptsListlen:integer=46;
    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: 8; text: 'Prerequisites       '),
              (typeid: 2; text: 'Unknown 014         '),
              (typeid: 1; text: 'Unknown 015         '),
              (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 021         '),
              (typeid: 1; text: 'Unknown 022         '),
              (typeid: 2; text: 'Unknown 023         '),
              (typeid: 2; text: 'Unknown 024         '),
              (typeid: 4; text: 'Owner               '),
              (typeid: 2; text: 'Unknown 026         '),
              (typeid: 2; text: 'Unknown 027         '),
              (typeid: 1; text: 'Unknown 028         '),
              (typeid: 1; text: 'Unknown 029         '),
              (typeid: 1; text: 'Unknown 030         '),
              (typeid: 1; text: 'Unknown 031         '),
              (typeid: 2; text: 'Sight               '),
              (typeid: 6; text: 'Movement type       '),
              (typeid: 2; text: 'Unknown 034         '),
              (typeid: 2; text: 'Speed               '),
              (typeid: 2; text: 'Unknown 036         '),
              (typeid: 2; text: 'Unit gfxID          '),
              (typeid: 2; text: 'Turret gfxID        '),
              (typeid: 2; text: 'Unknown 039         '),
              (typeid: 2; text: 'Unknown 040         '),
              (typeid: 2; text: 'Unknown 041         '),
              (typeid: 2; text: 'Weapon rate of fire '),
              (typeid: 2; text: 'Weapon range        '),
              (typeid: 2; text: 'Weapon damage       '),
              (typeid: 2; text: 'Unknown 045         '),
              (typeid: 7; text: 'Weapon type         '),
              (typeid: 2; text: 'Unknown 047         '),
              (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:integer=193930;
    structOptsListlen:integer=55;
    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: 1; text: 'Unknown 013         '),
              (typeid: 8; text: 'Prerequisites       '),
              (typeid: 1; text: 'Unknown 015         '),
              (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: 'Is repair facility? '),
              (typeid: 2; text: 'Units can enter?    '),
              (typeid: 2; text: 'Spice storage       '),
              (typeid: 2; text: 'Power consumed      '),
              (typeid:10; 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: 2; text: 'Constr. option #1   '),
              (typeid: 2; text: 'Constr. option #2   '),
              (typeid: 2; text: 'Constr. option #3   '),
              (typeid: 2; text: 'Constr. option #4   '),
              (typeid: 2; text: 'Constr. option #5   '),
              (typeid: 2; text: 'Constr. option #6   '),
              (typeid: 2; text: 'Constr. option #7   '),
              (typeid: 2; 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: ''));
      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));

// =========================================
//                 MISCELLANEOUS
// =========================================

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;


procedure cntin;
var pause: char;
begin
   repeat until keypressed;
    pause:=ReadKey();
end; {cntin}

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;


// =========================================
//                 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();
    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;

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 writeHex4(inputhex:fourbyte;address:Integer);
var i:integer;
begin
  for i:=0 to 3 do
    writeByte(address+3-i,inputhex[i]);
end;

// =========================================
//              DATA & OFFSETS
// =========================================

function getDataLen(opttype:integer):integer;
begin
  case opttype of
   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;  // structures
   9 : getDataLen:=2;  // unit
  10 : getDataLen:=2;  // foundation
   end;
end;

function getListLen(toindex:integer):integer;
var i:integer;
begin
  getListLen:=0;
  for i:=0 to toindex-1 do
  begin
    getListLen:=getListLen+getDataLen(lev3list[i].typeid);
  end;
end;

function getAddress(unitindex,optindex:integer):integer ;
begin
  getAddress:=optsOffset + (unitindex*getListLen(listlen[3]+1)) + getListLen(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[i])*multiplier);
     multiplier:=multiplier*256;
   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(unitindex,optindex:integer):integer;
var inputhex:fourbyte;
    blank:boolean;
    i:integer;
begin
  inputhex:=readHex4(getAddress(unitindex,optindex));
  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 getRefString(unitindex,optindex:integer):string;
var inputhex:fourbyte;
    blank:boolean;
    i:integer;
    ref:integer;
begin
  inputhex:=readHex4(getAddress(unitindex,optindex));
  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 getRefBytesString(unitindex,optindex:integer):string;
var inputhex:fourbyte;
begin
  inputhex:=readHex4(getAddress(unitindex,optindex));
  getRefBytesString:=concat(  byte2hexStr(inputhex[0])+' '
                            + byte2hexStr(inputhex[1])+' '
                            + byte2hexStr(inputhex[2])+' '
                            + byte2hexStr(inputhex[3])
                           );
end;

function get2Byte(unitindex,optindex:integer):integer;
begin
  get2Byte:=readByte(getAddress(unitindex,optindex));
  get2Byte:=get2byte+readByte(getAddress(unitindex,optindex)+1)*256;
  if get2Byte>$7FFF then get2Byte:=get2Byte-$10000;
end;

function getByte(unitindex,optindex:integer;signed:boolean):integer;
begin
  getByte:=readByte(getAddress(unitindex,optindex));
  if signed and (getByte>$7F) then getByte:=getByte-$100;
end;

function readOwner(unitindex,optindex:integer):ownership;
var
    i:integer;
begin
  for i:=1 to 6 do readOwner[i]:=false;
  i:=readByte(getAddress(unitindex,optindex));
  if  (i mod 2)        = 1 then readOwner[1]:=true;
  if ((i div 2) mod 2) = 1 then readOwner[2]:=true;
  if ((i div 4) mod 2) = 1 then readOwner[3]:=true;
  if ((i div 8) mod 2) = 1 then readOwner[4]:=true;
  if ((i div 16)mod 2) = 1 then readOwner[5]:=true;
  if ((i div 32)mod 2) = 1 then readOwner[6]:=true;
end;

procedure writeOwners(o:ownership;unitindex,optindex:integer);
var
    b:byte;
    i,multiplier:integer;
begin
  multiplier:=1;
  b:=0;
  for i:=1 to 6 do
  begin
   if o[i] then b:=byte(b+multiplier);
   multiplier:=multiplier*2;
  end;
  writeByte(getAddress(unitindex,optindex),b);
end;


function getOwnerstr(unitindex,optindex:integer):string;
var
    o:ownership;
    i:integer;
begin
  o:=readOwner(unitindex,optindex);
  getOwnerstr:='';
  for i:=1 to 6 do
    if o[i] then getOwnerstr:=getOwnerstr+ copy(owners[i],1,2) + ',';
  if length(getOwnerstr)>0 then getOwnerstr:=copy(getOwnerstr,1,length(getOwnerstr)-1);
end;

function getMovementTypestr(unitindex,optindex:integer):string;
var
    i:integer;
begin
  i:=readByte(getAddress(unitindex,optindex));
  getMovementTypestr:='';
  case i of
   0 :getMovementTypestr:='Foot';
   1 :getMovementTypestr:='Tracked';
   2 :getMovementTypestr:='Harvester';
   3 :getMovementTypestr:='Wheeled';
   4 :getMovementTypestr:='Flying';
   5 :getMovementTypestr:='SandSlither';
 end;
end;

function getDataStr(unitindex,optindex:integer):string;
begin
  getDataStr:='';
  case lev3list[optindex].typeid of
   1 : getDataStr:=tostring(getByte(unitindex,optindex,false));               // Unsigned byte
   2 : getDataStr:=toString(get2Byte(unitindex,optindex));                    // 2-byte
   3 : getDataStr:=getRefString(unitindex,optindex);                          // 4-byte (REF)
   4 : getDataStr:=getOwnerstr(unitindex,optindex);                           // owner
   5 : getDataStr:=toString(get2Byte(unitindex,optindex));                    // command
   6 : getDataStr:=getMovementTypestr(unitindex,optindex);                    // Movement type
   7 : getDataStr:=toString(get2Byte(unitindex,optindex));                    // weapon type
   8 : getDataStr:=toString(get2Byte(unitindex,optindex));                    // structures
   9 : getDataStr:=toString(get2Byte(unitindex,optindex));                    // unit
   0 : getDataStr:=byte2hexstr(readByte(getAddress(unitindex,optindex))); // Unsigned byte (hex)
   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(bytes:integer):fourbyte;
var charin: char;
    inputstr:String[10];
    i:integer;
begin
   charin:=char(0);
   inputstr:='';
   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) then write(' ');
     end
    else if (charin=keymap.back) and (Length(inputstr)>0) then
     begin
       if ((length(inputstr) mod 2)=0) 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 drawLine(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 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]));
      drawLine(x+m-1,y+1,h-2,char(framechar[charset,6]));
    end;
   gotoxy(0,0);
end; {drawFrame}


// =========================================
//             SELECTION DISPLAY
// =========================================

procedure drawSelect(l:integer);
begin
   drawLine(selectBoundaries[l][1],selectBoundaries[l][2],maxdisplay[l]+1,' ');
   gotoxy(selectBoundaries[l][1],selectBoundaries[l][2]+select[l]);
   write('[');
   drawLine(selectBoundaries[l][3],selectBoundaries[l][2],maxdisplay[l]+1,' ');
   gotoxy(selectBoundaries[l][3],selectBoundaries[l][2]+select[l]);
   write(']');
   gotoxy(1,25);
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 drawOwners(unitindex,optindex:integer);
var
    i:integer;
    o:ownership;
begin
  o:=readOwner(unitindex,optindex);
  maxdisplay[4]:=5;
  listlen[4]:=5;
  for i:=1 to 6 do
   begin
     gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]+i-1);
     if o[i] then write(uppercase(owners[i]))
     else write(lowercase(owners[i]));
     drawSelect();
   end;
end;



procedure drawLev4();
var
    unitindex,optindex:integer;
begin
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  case lev3list[select[3]+scroll[3]].typeid of
    1 : ;                                   //signed byte
    2 : ;                                   //2-byte
    3 : ;                                   //4-byte
    4 : drawOwners(unitindex,optindex);     //owner
    5 : ;                                   //command
    6 : ;                                   //Movement type
    7 : ;                                   //weapon type
    8 : ;                                   //structures
    9 : ;                                   //unit
    10: ;                                   //build foundation
   end;
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[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;
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;
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;
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(unitindex,optindex,minval,maxval:integer);
var
    input:integer;
    curval:String;
begin
   maxdisplay[4]:=1;
   listlen[4]:=1;
   write('Old value : ');
   curval:=getDataStr(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
    end
   else if errormsg<> 'Canceled' then
    begin
      write(errormsg);
      cntin();
    end;
   gotoxy(1,25);
   level:=3;
end;

procedure showinputOffset(unitindex,optindex:integer);
var
    input:integer;
    curval:integer;
begin
   maxdisplay[4]:=1;
   listlen[4]:=1;
   write('Old value : ');
   curval:=getRefAddress(unitindex,optindex);
   write(curval);
   write(' (');
   write(getRefBytesString(unitindex,optindex));
   write(')');
   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));
   gotoxy(selectBoundaries[4][1]+32,selectBoundaries[4][2]);
   if length(errormsg)=0 then
    begin
      // SAVE
    end
   else if errormsg<> 'Canceled' then
    begin
      write(errormsg);
      cntin();
    end;
   gotoxy(1,25);
   level:=3;
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,50,22,27,level=3,false);
   4 : drawFrame(15,5,52,15,-1,level=4,true);
  end;
end;

procedure drawLevFrame();
begin
  drawLevFrame(level);
end;

procedure clearWorkArea();
var i: integer;
begin
    for i:=3 to 25 do
      begin
        gotoxy(1,i);
        clreol();
      end;
end;

procedure drawLayout4();
var
    unitindex,optindex:integer;
begin
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  drawLevFrame(3);
  drawLevFrame(level);
  gotoxy(17,6);
  write('Section   : ');
  write(lev2list[unitindex]);
  gotoxy(17,7);
  write('Option    : ');
  write(lev3list[optindex].text);
  gotoxy(17,8);
  write('Data type : ');
  write(typeids[lev3list[optindex].typeid][1]);
  gotoxy(29,9);
  write(typeids[lev3list[optindex].typeid][2]);
  gotoxy(17,10);

  case lev3list[select[3]+scroll[3]].typeid of
    1 : showinputInt(unitindex,optindex,-128,127);                 //signed byte
    2 : showinputInt(unitindex,optindex,-32768,32767);             //2-byte
    3 : showinputOffset(unitindex,optindex);                       //4-byte
    4 : drawOwners(unitindex,optindex);                            //owner
    5 : showinputInt(unitindex,optindex,-32768,32767);             //command
    6 : showinputInt(unitindex,optindex,-32768,32767);             //Movement type
    7 : showinputInt(unitindex,optindex,-32768,32767);             //weapon type
    8 : showinputInt(unitindex,optindex,0,255);                    //structures
    9 : showinputInt(unitindex,optindex,0,255);                    //unit
    10: showinputInt(unitindex,optindex,0,255);                    //build foundation
   end;
  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()
   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;
     optsOffset:=UnitOptsOffset;
     listlen[2]:=UnitListLen;
     for i:=0 to listlen[2] do
       lev2List[i]:=getRefString(i,1);
   end
  else if select[1]=1 then
   // LOAD STRUCTURES LIST DATA
   begin
     listlen[3]:=structOptsListLen;
     lev3List:=structOptsList;
     optsOffset:=StructOptsOffset;
     listlen[2]:=StructListLen;
     for i:=0 to listlen[2] do
       lev2List[i]:=getRefString(i,1);
  end;
end;
// =========================================
//              INPUT PROCEDURES
// =========================================

procedure inputOwners(unitindex,optindex:integer);
var
 o:ownership;
 listpos:integer;
begin
  if keyread=keymap.SPACE then
   begin
     o:=readOwner(unitindex,optindex);
     listpos:=select[4]+scroll[4];
     o[listpos+1]:=(not o[listpos+1]);
     writeOwners(o,unitindex,optindex);
     drawOwners(unitindex,optindex);
   end
  else if keyread=keymap.ENTER then
  begin
    level:=3;
    FillWorkArea();
  end;
end;



procedure inputLev4();
var
    unitindex,optindex:integer;
begin
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  case lev3list[select[3]+scroll[3]].typeid of
    1 : ;                                       //signed byte
    2 : ;                                       //2-byte
    3 : ;                                       //4-byte
    4 : inputOwners(unitindex,optindex);        //owner
    5 : ;                                       //command
    6 : ;                                       //Movement type
    7 : ;                                       //weapon type
    8 : ;                                       //structures
    9 : ;                                       //unit
    10: ;                                       //build foundation
   end;
end;

// =========================================
//                NAVIGATION
// =========================================

procedure MoveDown(lines:integer);
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
    // scroll
    begin
      scroll[level]:=scroll[level]+lines;
      if scroll[level] > (listlen[level]-maxdisplay[level]) then scroll[level]:=(listlen[level]-maxdisplay[level]);
    end;
  drawList();
  drawSelects();
  if level=1 then begin updateLev2Lists(); updateLists(); end;
  if level=2 then drawLev3data();
end;

procedure MoveUp(lines:integer);
begin
  if ((select[level]-lines)>0) then
    // move
    select[level]:=select[level]-lines
  else
    // move beyond visible field
    begin
     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;
  drawList();
  drawSelects();
  if level=1 then begin updateLev2Lists(); updateLists(); end;
  if level=2 then drawLev3data();
end;

procedure MoveRight();
begin
  if level=3 then level:=1
  else level:=level+1;
  FillWorkArea();
end;

procedure MoveLeft();
begin
  if level=1 then level:=3
  else level:=level-1;
  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 begin FillWorkArea; level:=4; drawLayout; end
  else if (keyread=keymap.RIGHT) and (level<>4) then MoveRight()
  else if (keyread=keymap.LEFT)  and (level<>4) then MoveLeft()
  else if (keyread=keymap.DOWN)  then MoveDown(1)
  else if (keyread=keymap.UP)    then MoveUp(1)
  else if (keyread=keymap.PGDN)  then MoveDown(maxdisplay[level])
  else if (keyread=keymap.PGUP)  then MoveUp(maxdisplay[level])
  else if (keyread=keymap.ENDK)  then MoveDown(listlen[level])
  else if (keyread=keymap.HOME)  then MoveUp(listlen[level])
  else if (level=4) and (keyread=keymap.ESC) then begin level:=3; FillWorkArea(); end
  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:=1 to 25 do
  begin
    gotoxy(1,i);
    clreol;
  end;
  gotoxy(1,1);
  for i:=1 to 80 do
    write(char(framechar[2,5]));
  gotoxy(3,1);
  write(' Dune II 1.07 Editor ');
  gotoxy(65,1);
  write(' by Nyerguds ');
  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
  if not (openfile('DUNE2.EXE')) then cntin
  else if (readString(228274) = 'DUNE2.EXE') then
   begin
    init();
    drawLayout();
    keyread:=char(0);
      while keyread <> char(255) do
      begin
        keyread:=ReadKey();
        gotoxy(1,25);
        input();
      end;
    closeFile();
   end
 else
  begin
    writeln('Invalid "DUNE2.EXE" file.');
    writeln('This program only works on the 1.07 version of Dune II.');
    cntin;
  end;
end.

