// Program written by Maarten Meuris AKA Nyerguds
program dune_edit;
uses crt, dos;

TYPE
   // For storing layout characters
   chset      = array [1..8] of byte;
   // For coordinates of layout frames
   coords     = array [1..4] of integer;
   // For storing a 4-byte value as booleans
   boollist   = array [0..32] of boolean;
   // For storing a 4-byte value as bytes
   // ALWAYS USE AS LITTLE-ENDIAN!!! The read & write methods convert it to correspond to the big-endian in the exe file
   fourbyte   = array [0..3] of byte;

   // For editing description for each data type
   descr      = array [1..2] of string[36];
   // For help description for each data type
   bigdescr   = array [1..7] of string[48];

   // Type designed for data types where the list index in the editor does not correspond to the internal value
   values = Record
      val:integer;
      txt:string[30];
   end;

   // Input key codes - "endk" is the [END] key
   keymapper = Record
      up, down, left, right, space, enter, esc, back, pgup, pgdn, home, endk, f1, f2, f3, f4, f11, f12 :char;
   end;

   // Type for the data identification table
   option = Record
      typeid: Integer;
      text: String[23];
   end;

   // put in specific types to have a single type for the unit and structure lists
   lev2Lst = array[0..39] of String;
   lev3Lst = array[0..100] of Option;

var
    // game version / which list is active / level to return to after level #4 / option list lengths (calculated on init)
    gamever,level,prevlev:integer;

    //option list lengths (calculated on init)
    OptsListlengths:array[0..2] of integer;

    // Binary editing mode enabled / Hexadecimal display mode enabled
    binEd,HexDisplay:boolean;
    // File to edit
    exefile:file;
    // input
    keyread:char;
    // Error message when editing fails or cancels
    errormsg:String;
    // filename string
    gamenamestr:String[9];
    // complete file path string
    gamepathstr:String;
    // navigation & scrolling data for all 4 levels
    select,scroll,maxdisplay,listlen:array [1..4] of integer;
    // Display lists to print on screen
    lev2List,lev4List:lev2Lst;
    lev3List:lev3Lst;
    lev3data:array [0..19] of String[20];

const
    // Input key codes
    keymap:keymapper = ( up:    char(072); down:  char(080);
                         left:  char(075); right: char(077);
                         space: char(032); enter: char(013);
                         esc:   char(027); back:  char(008);
                         pgup:  char(073); pgdn:  char(081);
                         home:  char(071); endk:  char(079);
                         f1:    char(059); f2:    char(060);
                         f3:    char(061); f4:    char(062);
                         f11:   char(133); f12:   char(134));

    // DOS ASCII character codes for drawing frames; the two types are Active and Inactive
    framechar:array[1..2] of chset = (
                       //  |"  "|   |_  _|     -    |   "|"  _|_
                         ($DA, $BF, $C0, $D9, $C4, $B3, $C2, $C1),
                         ($C9, $BB, $C8, $BC, $CD, $BA, $CB, $CA));

    //colors
    // Selected item on active level
    activeitemcolor:byte = Yellow;
    activeitembgcolor:byte = LightRed;
    // Selected item on inactive level
    inactiveitemcolor:byte = White;
    inactiveitembgcolor:byte = Black;
    // nonselected item on active level
    activelevcolor:byte = white;
    activelevbgcolor:byte = Blue;
    // nonselected item on inactive level
    inactivelevcolor:byte = LightGray;
    inactivelevbgcolor:byte = Blue;

    // General info Strings
    editorname:string[14] = 'Dune II Editor';
    editorversion:string[10] = 'v1.13.1';
    editorversiondate:string[30] = 'September 11, 2009';
    editorauthor:string[8] = 'Nyerguds';
    editorauthoremail:string[20] = 'nyerguds@gmail.com';
    defnamestr:String[9] = 'DUNE2.EXE';

    // == LAYOUT CONTROL ==
    // Coordinates of layout frames
    selectBoundaries:array[1..4] of coords = (
                         (3,4,25,6),   // level 1 list
                         (3,9,25,23),  // level 2 list
                         (30,4,54,23), // level 3 list
                         (17,12,39,18) // level 4 list
                         );
    selectionLengths:array[1..4] of integer = (23,23,25,33);

    // WARNING, NOT all of these are coordinates:
    // they are upper left corner X and Y, width, and height
    drawBoundaries:array[1..4] of coords = (
                         (1,3,27,5),   // level 1 frame
                         (1,8,27,17),  // level 2 frame
                         (28,3,53,22), // level 3 frame
                         (15,5,52,15)  // level 4 frame
                         );
    secondcolumn:array[1..4] of integer = (-1,-1,29,-1);
    secondcolumnSelection:integer=21;
    // == END OF LAYOUT CONTROL ==


    // == DUNE II VERSION CONTROL ==
    // When adding a version, all array lengths must be updated!
    //place of the identification string (gamenamestr) in each version
    verAddress:array[0..4] of integer = (225278,229282,228274,229682,229586);
    // String to display for each version
    dunever:   array[0..4] of string[7] = ('DEMO','1.00','1.07-US','1.07-EU','1.07-HS');
    // Correction to make file addresses out of exe references for each version
    refAddress:array[0..4] of fourbyte = (($32, $4D, $90, $F0),  // demo
                                          ($33, $2B, $81, $10),  // 1.00
                                          ($32, $EC, $85, $00),  // 1.07-US
                                          ($33, $44, $7F, $80),  // 1.07-EU
                                          ($33, $3E, $7F, $E0)); // 1.07-HS
    // Start offset of unit/struct/houses data for each version
    TypeOffset:array[0..2] of array[0..4] of integer= (
                         // demo   1.00   1.07US 1.07EU 1.07HS
                           (201840,198480,195760,195840,195760), // units offsets
                           (199930,196570,193930,194010,193930), // structures offsets
                           (237898,242308,240988,243832,243724)  // houses offsets
                           );
    // == END DUNE II VERSION CONTROL ==


    // == LISTS CONTROL ==
    // When adding a list, all array lengths must be updated!

    // Main list
    mainlist:array[0..2] of String[20] = ('Units       ',
                                          'Structures  ',
                                          'Houses      ');
    // Number of units & structures - 1 , since first element is #0)
    typeListLen:array[0..2] of integer = (
                                           26,  // units list length
                                           18,  // structures list length
                                           5    // houses list length
                                          );
    // REFERENCE OPTION: the position of the option in the list
    // which holds this type's name reference
    refoption:array[0..2] of integer = (
                                           1,  // units list name option
                                           1,  // structures list name option
                                           0   // houses list name option
                                       );
    // Option lists for units/structs
    // Size needs to be adjusted with typeid:-1 values to correspond to the lev3Lst type.
    TypeOptsList:array[0..2] of lev3Lst = (
    // unitOptsList
         (
              (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: 12; text: 'Air unit shadow        '),
              (typeid: 13; text: 'Unknown 005 (bit 2)    '),
              (typeid: 14; text: 'Unknown 005 (bit 3)    '),
              (typeid: 15; text: 'Unknown 005 (bit 4)    '),
              (typeid: 16; text: 'Unknown 005 (bit 5)    '),
              (typeid: 17; text: 'Worm camouflage        '),
              (typeid: 18; text: 'Turret Rotates         '),
              (typeid: 19; text: 'Unknown 005 (bit 8)    '),
              (typeid: 12; text: 'Pick up for repairs    '),
              (typeid: 13; text: 'Unknown 006 (bit 2)    '),
              (typeid: 14; text: 'Always use full scripts'), // 'ForceScriptCycles' - relates to TacticalPos
              (typeid: 15; text: 'Unknown 006 (bit 4)    '), // Active even in shroud?
              (typeid: 16; text: 'Can target air units   '),
              (typeid: 17; text: 'Unknown 006 (bit 6)    '),
              (typeid: 18; text: 'Unknown 006 (bit 7)    '),
              (typeid: 19; text: 'Unknown 006 (bit 8)    '),
              (typeid:  2; text: 'Unknown 007            '),
              (typeid:  2; text: 'Hit points             '),
              (typeid:  2; text: 'Sight                  '),
              (typeid:  2; text: 'Sidebar icon gfxID     '),
              (typeid:  2; text: 'Cost                   '),
              (typeid:  2; text: 'Build Time             '),
              (typeid:  2; text: 'Tech level             '),
              (typeid: 10; text: 'Prerequisites          '),
              (typeid:  1; text: 'Unknown 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: 'Threat level for AI    '),
              (typeid:  4; text: 'Owner                  '),
              (typeid:  2; text: 'Unit array range min   '),
              (typeid:  2; text: 'Unit array range max   '),
              (typeid: 12; text: 'Unknown 028 (bit 1)    '),
              (typeid: 13; text: 'Explode on target      '),
              (typeid: 14; text: 'Explode when dying     '),
              (typeid: 15; text: 'Sonic Immunity         '),
              (typeid: 16; text: 'Bumpy Movement         '),
              (typeid: 17; text: 'Tracked crushing       '),
              (typeid: 18; text: 'Has user control       '),
              (typeid: 19; text: 'Stay on map            '),
              (typeid: 12; text: 'Unknown 029 (bit 1)    '),
              (typeid: 13; text: 'Unknown 029 (bit 2)    '),
              (typeid: 14; text: 'Fires Twice            '),
              (typeid: 15; text: 'Unknown 029 (bit 4)    '),
              (typeid: 16; text: 'Unknown 029 (bit 5)    '),
              (typeid: 17; text: 'Unknown 029 (bit 6)    '),
              (typeid: 18; text: 'Unknown 029 (bit 7)    '),
              (typeid: 19; text: 'Not a weapon type      '),
              (typeid:  1; text: 'Unknown 030            '),
              (typeid:  1; text: 'Unknown 031            '),
              (typeid:  2; text: 'Unknown 032            '),
              (typeid:  6; text: 'Movement type          '),
              (typeid:  2; text: 'Move animation speed   '),
              (typeid:  2; text: 'Speed                  '),
              (typeid:  2; text: 'Turning speed          '),
              (typeid:  2; text: 'Unit gfxID             '),
              (typeid:  2; text: 'Turret gfxID           '),
              (typeid:  2; text: 'Unknown 039            '),
              (typeid:  2; text: 'Move animation #frames '),
              (typeid:  2; text: 'Death animation        '), // needs animations list added later!
              (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: 11; text: 'Weapon sound           '),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: '')
         ),
    //structOptsList
         (
              (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: 12; text: 'Unknown 005a (bit 1)   '),
              (typeid: 13; text: 'Construction screen    '),
              (typeid: 14; text: 'Unknown 005a (bit 3)   '),
              (typeid: 15; text: 'No concrete needed     '),
              (typeid: 16; text: 'Unknown 005a (bit 5)   '),
              (typeid: 17; text: 'Unknown 005a (bit 6)   '),
              (typeid: 18; text: 'Unknown 005a (bit 7)   '),
              (typeid: 19; text: 'Can be captured        '),
              (typeid:  1; text: 'Unknown 005b           '),
              (typeid: 20; text: 'Infantry spawn cells   '),
              (typeid:  2; text: 'Hit points             '),
              (typeid:  2; text: 'Sight                  '),
              (typeid:  2; text: 'Sidebar icon gfxID     '),
              (typeid:  2; text: 'Cost                   '),
              (typeid:  2; text: 'Build Time             '),
              (typeid:  2; text: 'Tech level             '),
              (typeid: 10; text: 'Prerequisites          '),
              (typeid:  1; text: 'Build order            '),
              (typeid:  1; text: 'CY upgrades needed     '),
              (typeid:  1; text: 'Unknown 016            '),
              (typeid:  1; text: 'Unknown 017            '),
              (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:  2; text: 'Structure ID           '),
              (typeid:  2; text: 'Weapon damage          '),
              (typeid:  2; text: 'Threat level for AI    '),
              (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: 'Structure gfxID        '),
              (typeid:  2; text: 'Unknown 035            '),
              (typeid:  2; text: 'Unknown 036            '),
              (typeid:  2; text: 'Unknown 037            '),
              (typeid:  2; text: 'Unknown 038            '),
              (typeid:  2; text: 'Unknown 039            '),
              (typeid:  2; text: 'Unknown 040            '),
              (typeid:  9; text: 'Construction optn #1   '),
              (typeid:  9; text: 'Construction optn #2   '),
              (typeid:  9; text: 'Construction optn #3   '),
              (typeid:  9; text: 'Construction optn #4   '),
              (typeid:  9; text: 'Construction optn #5   '),
              (typeid:  9; text: 'Construction optn #6   '),
              (typeid:  9; text: 'Construction optn #7   '),
              (typeid:  9; text: 'Construction optn #8   '),
              (typeid:  2; text: '1st upgrade techlvl    '),
              (typeid:  2; text: '2nd upgrade techlvl    '),
              (typeid:  2; text: '3rd upgrade techlvl    '),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: ''),
              (typeid: -1; text: ''), (typeid: -1; text: '')
         ),
    // houseOptsList
         (
              (typeid:  3; text: 'Name code ref.         '),
              (typeid: 20; text: 'Weakness to Deviation  '),
              (typeid:  2; text: 'LemonFactor (?)        '),
              (typeid:  2; text: 'Decay                  '),
              (typeid:  2; text: 'Radar Color Index      '),
              (typeid:  2; text: 'Palace recharge time   '),
              (typeid:  2; text: 'Frigate delay time     '),
              (typeid:  2; text: 'House letter           '), // 'H' // NEED CHAR TYPE INPUT FUNCTION!!!!!
              (typeid:  2; text: 'Palace Special         '),
              (typeid: 21; text: 'Win music              '),
              (typeid: 21; text: 'Lose music             '),
              (typeid: 21; text: 'Mentat music           '),
              (typeid:  3; text: 'House voice ref.       '),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: ''),
              (typeid: -1; text: ''),(typeid: -1; text: ''),(typeid: -1; text: '')
         )
    );
    // == END OF LISTS CONTROL ==


    // == TYPES CONTROL ==

    // Editing description for each data type
    typedescr:array[0..21] of descr = (
    {00} ('Boolean (Yes/No)                   ',''),
    {01} ('Single byte (-128,127)             ',''),
    {02} ('Double byte (-32768,32767)         ',''),
    {03} ('Reference to 0-terminated string.  ','Give decimal offset in "DUNE2.EXE".'),
    {04} ('Ownership. Press space to select or','deselect a side.                   '),
    {05} ('Sidebar command. Select one from   ','the list.                          '),
    {06} ('Movement type.  Select one from the','list.                              '),
    {07} ('Weapon type. Select one from the   ','list.                              '),
    {08} ('Foundation dimensions. Select one  ','from the list.                     '),
    {09} ('Construction option. Select one    ','unit from the list.                '),
    {10} ('Prerquisite structures. Press space','to select or deselect a structure. '),
    {11} ('Weapon Sounds. Select one from the ','list.                              '),
    {12} ('Boolean (Yes/No)                   ',''),
    {13} ('Boolean (Yes/No)                   ',''),
    {14} ('Boolean (Yes/No)                   ',''),
    {15} ('Boolean (Yes/No)                   ',''),
    {16} ('Boolean (Yes/No)                   ',''),
    {17} ('Boolean (Yes/No)                   ',''),
    {18} ('Boolean (Yes/No)                   ',''),
    {19} ('Boolean (Yes/No)                   ',''),
    {20} ('Percentage. Select a value from the','list.'),
    {21} ('Music. Select a track from the     ','list.')
    );

    // Help info for each data type
    typehelp:array[0..21] of bigdescr = (
    //    [________________________________________________] = max length for each string
    {00} ('BOOLEANS are simple Yes/No values.','','','','','',''),
    {01} ('SINGLE BYTES range from -128 to 127.','','','','','',''),
    {02} ('DOUBLE BYTES are the most common data type.','They range from -32768 to 32767.','','','','',''),
    {03} ('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.'),
    {04} ('The OWNERSHIP value is a list of switches that','allow or deny specific houses the right to build','this specific unit or structure.','','','',''),
    {05} ('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.'),
    {06} ('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.','',''),
    {07} ('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.','',''),
    {08} ('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.','',''),
    {09} ('Choosing a CONSTRUCTION OPTION gives a building','a unit to add to its production options list.','','','','',''),
    {10} ('The PREREQUISITE buildings are the buildings a','player needs to have before he can build this','unit or building.','','','',''),
    {11} ('SOUNDS are played when a unit attacks or when a','weapon is fired.','The sounds indicated with "VOC" have a .VOC file','that is played. The rest only give generated','sounds.','',''),
    {12} ('BOOLEANS are simple Yes/No values.','','','','','',''),
    {13} ('BOOLEANS are simple Yes/No values.','','','','','',''),
    {14} ('BOOLEANS are simple Yes/No values.','','','','','',''),
    {15} ('BOOLEANS are simple Yes/No values.','','','','','',''),
    {16} ('BOOLEANS are simple Yes/No values.','','','','','',''),
    {17} ('BOOLEANS are simple Yes/No values.','','','','','',''),
    {18} ('BOOLEANS are simple Yes/No values.','','','','','',''),
    {19} ('BOOLEANS are simple Yes/No values.','','','','','',''),
    //    [________________________________________________] = max length for each string
    {20} ('PERCENTAGES are actually not stored as part of','100, but as part of 256. The list only has rough','values. You can use [F3] or [F4] for more','detailed editing.','','',''),
    {21} ('The MUSIC tracks are the MIDI tracks in the','game.','','','','','')
    );

    // Strings for specific data types
    booleans:array[0..1] of string[3] = ('No','Yes');
    movementTypes:array[0..5] of string[9] = ('Foot','Tracked','Harvester',
                                              'Wheeled','Flying','Slither');
    commands:array[0..13] of string[10] = ('Attack','Move','Retreat','Guard',
                                          'Area Guard','Harvest','Return','Stop',
                                          'Ambush','Sabotage','Die','Hunt',
                                          'Deploy','Destruct');
    foundations:array[0..6] of string[3] = ('1x1','2x1','1x2','2x2','2x3','3x2','3x3');

    sounds:array[0..35] of values = (
              (val:-1; txt: 'None                       '),
              (val: 0; txt: 'Not Applicable             '),
              (val:20; txt: 'Place down                 '),
              (val:22; txt: 'Twinkling                  '),
              (val:23; txt: 'Intro chord                '),
              (val:24; txt: 'Carryall dropoff           '),
              (val:30; txt: 'Infantry kill 1       (VOC)'),
              (val:35; txt: 'Infantry kill 2       (VOC)'),
              (val:33; txt: 'Infantry kill 3       (VOC)'),
              (val:34; txt: 'Infantry kill 4       (VOC)'),
              (val:31; txt: 'Infantry kill 5       (VOC)'),
              (val:36; txt: 'Infantry crush        (VOC)'),
              (val:38; txt: 'Click                 (VOC)'),
              (val:39; txt: 'Missile               (VOC)'),
              (val:40; txt: 'Hit sand              (VOC)'),
              (val:41; txt: 'Tank shot             (VOC)'),
              (val:42; txt: 'Rocket                (VOC)'),
              (val:43; txt: 'Sonic wave                 '),
              (val:44; txt: 'Structure crumble     (VOC)'),
              (val:46; txt: 'Death Hand target          '),
              (val:47; txt: '"Cannot place" beep        '),
              (val:49; txt: 'Tank shot             (VOC)'),
              (val:55; txt: 'Tank shot             (VOC)'),
              (val:51; txt: 'Exploding unit        (VOC)'),
              (val:52; txt: 'Money increase             '),
              (val:53; txt: 'Money decrease             '),
              (val:54; txt: 'Weapon hit                 '),
              (val:56; txt: 'Tank shot             (VOC)'),
              (val:57; txt: 'Tank shot             (VOC)'),
              (val:58; txt: 'Single gun            (VOC)'),
              (val:59; txt: 'Machinegun            (VOC)'),
              (val:60; txt: '"Constr. Complete"    (VOC)'),
              (val:61; txt: 'Plingplong                 '),
              (val:62; txt: 'Radar static          (VOC)'),
              (val:63; txt: 'Worm attack           (VOC)'),
              (val:64; txt: 'Mini Rocket           (VOC)')
              );

    music:array[0..34] of values = (
              (val:-1; txt: 'None                       '),
              (val: 1; txt: 'Ambient 1                 '),
              (val: 2; txt: 'Ordos Defeat              '),
              (val: 3; txt: 'Harkonnen Defeat          '),
              (val: 4; txt: 'Atreides Defeat           '),
              (val: 5; txt: 'Ordos Win                 '),
              (val: 6; txt: 'Harkonnen Win             '),
              (val: 7; txt: 'Atreides Win              '),
              (val: 8; txt: 'Ambient 2                 '),
              (val: 9; txt: 'Ambient 3                 '),
              (val:10; txt: 'Ambient 4                 '),
              (val:11; txt: 'Ambient 5                 '),
              (val:12; txt: 'Ambient 6                 '),
              (val:13; txt: 'Ambient 7                 '),
              (val:14; txt: 'Ambient 8                 '),
              (val:15; txt: 'Ambient 9                 '),
              (val:16; txt: 'Ambient 10                '),
              (val:17; txt: 'Attack 1                  '),
              (val:18; txt: 'Attack 2                  '),
              (val:19; txt: 'Attack 3                  '),
              (val:20; txt: 'Attack 4                  '),
              (val:21; txt: 'Attack 5                  '),
              (val:22; txt: 'Attack 6                  '),
              (val:24; txt: 'Harkonnen Mentat          '),
              (val:25; txt: 'Atreides Mentat           '),
              (val:26; txt: 'Ordos Mentat              '),
              (val:27; txt: 'Intro                     '),
              (val:28; txt: 'Main Menu                 '),
              (val:29; txt: 'Map Select                '),
              (val:30; txt: 'Harkonnen Ending          '),
              (val:31; txt: 'Atreides Ending           '),
              (val:32; txt: 'Ordos Ending              '),
              (val:33; txt: 'Ending Credits            '),
              (val:34; txt: 'Interlude                 '),
              (val:36; txt: 'Westwood Logo             ')
              );

    percentages:array[0..4] of values = (
              (val: $00; txt: '00 %               '),
              (val: $40; txt: '25 %               '),
              (val: $80; txt: '50 %               '),
              (val: $C0; txt: '75 %               '),
              (val:$100; txt: '100%               ')
              );
    // == END OF TYPES CONTROL ==

// =========================================
//                 MISCELLANEOUS
// =========================================

// Press Key To Continue procedure with buffer clear.
procedure cntin(showMsg: boolean);
begin
   if showMsg then
    begin
     writeln();
     write('Press any key to continue. . . ');
    end;
   repeat until keypressed;
    ReadKey();
   while keypressed do ReadKey(); {Clears the keyboard buffer}
   if showMsg then writeln();
end; {cntin}

procedure cntin();
begin
  cntin(false);
end; {cntin}

// gets basic file name from full path string
function getfilenamefrompath(path: string): String;
var backslashpos:integer;
begin
  backslashpos:=Pos('\',path);
  while backslashpos > 0 do
  begin
    Delete(path, 1, backslashpos);
    backslashpos:=Pos('\',path);
  end;
  getfilenamefrompath:=path;
end;

// Sets cursor to end of page to 'hide' it.
procedure resetCursor();
begin
  gotoxy(80,25);
end;

function maxint(val1,val2:integer):integer;
begin
  if (val1 > val2) then maxint:=val1
  else                  maxint:=val2;
end;

function minint(val1,val2:integer):integer;
begin
  if (val1 < val2) then minint:=val1
  else                  minint:=val2;
end;


function capitalize(str:string):String;
begin
   if (byte(str[1])>=97) and (byte(str[1])<=122) then str[1]:=char(byte(str[1])-32);
   capitalize:=str;
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 GetWord(startstr:string;wordnumber:integer;delim:string):String;
begin
  startstr:=startstr+delim;
  while wordnumber > 1 do
    begin
     startstr:=copy(startstr,pos(delim,startstr)+length(delim),(length(startstr)-pos(delim,startstr)));
     wordnumber:=wordnumber-1;
    end;
  if pos(delim,startstr) > 0 then GetWord:=copy(startstr,1,pos(delim,startstr)-1)
  else GetWord:=startstr;
end;

// For abbreviating prerequisites & options to 4 characters
function smartAbbrev(toAbbrev:string):string;
var
  firstword:string;
  secondword:string;
begin
  smartAbbrev:='';

  if (pos(' ',toAbbrev) = 0) and (pos('-',toAbbrev) = 0) then
    begin
      firstword:=toAbbrev;
      secondword:='';
    end;
  if (pos(' ',toAbbrev) = 0)
     OR (   (pos(' ',toAbbrev) <> 0) and (pos('-',toAbbrev) <> 0)
             and (pos(' ',toAbbrev) > pos('-',toAbbrev))
        ) then
    begin
      firstword:=GetWord(toAbbrev,1,'-');
      secondword:=GetWord(toAbbrev,2,'-');
    end;

  if (pos('-',toAbbrev) = 0)
     OR (   (pos(' ',toAbbrev) <> 0) and (pos('-',toAbbrev) <> 0)
             and (pos(' ',toAbbrev) < pos('-',toAbbrev))
        ) then
    begin
      firstword:=GetWord(toAbbrev,1,' ');
      secondword:=GetWord(toAbbrev,2,' ');
    end;

    firstword:=capitalize(firstword);
    secondword:=capitalize(secondword);

  if SecondWord<>'' then
    begin
      if length(secondword)=1 then
        smartAbbrev:=copy(firstword,1,3) + secondword
      else if length(firstword)>2 then
        smartAbbrev:=copy(firstword,1,2) + copy(secondword,1,2)
      else
        smartAbbrev:=copy(concat(firstword,copy(secondword,1,3)),1,4);
    end
  else
    begin
      if isNumeric(copy(firstword,length(firstword)-2, 3)) then
        smartAbbrev:=copy(firstword,1,1) + Copy(firstword, length(firstword)-2, 3)
      else if isNumeric(Copy(firstword, length(firstword)-1, 2)) then
        smartAbbrev:=copy(firstword,1,2) + Copy(firstword, length(firstword)-1, 2)
      else if isNumeric(firstword[length(firstword)]) then
        smartAbbrev:=copy(firstword,1,3) + Copy(firstword, length(firstword), 1)
      else smartAbbrev:=copy(firstword,1,4);
    end;
end;

// To convert value to string on the fly without needing a variable to store it in.
function toString(b:byte):String;
begin
  str(b,toString);
end;

function toString(i:Integer):String;
begin
  str(i,toString);
end;

// General data display functions. Keep in mind that "fourbyte" is little-endian.

function fourbyte2int(data:fourbyte):integer;
var i:integer;
begin
  fourbyte2int:=0;
  for i:=0 to 3 do
    fourbyte2int:=fourbyte2int*$100+data[i];
end;

function int2fourbyte(data:integer):fourbyte;
var data2,i:integer;
begin
  data2:=data;
  if data >= 0 then
   for i:=3 downto 0 do
    begin
      int2fourbyte[i]:=data2 mod $100;
      data2:=data2 div $100;
    end
  else
   begin
     int2fourbyte[3]:=data2 mod $100;
     data2:=data2 div $100;
     for i:=2 downto 0 do
      begin
        int2fourbyte[i]:=(data2 mod $100)-1;
        data2:=data2 div $100;
      end;
   end;
end;

function hexchar2byte(hex:char):byte;
begin
  hexchar2byte:=0;
  if (byte(hex)>=65) and (byte(hex)<=70) then hexchar2byte:=byte(hex)-55
  else if (byte(hex)>=48) and (byte(hex)<=57) then hexchar2byte:=byte(hex)-48
end;

function hexstr2byte(hex:string[2]):byte;
var multiplier:integer;
begin
  hexstr2byte:=0;
  multiplier:=16;
  if (length(hex)=0) then hex:='00';
  if (length(hex)=1) then hex:=concat('0' + hex);
  hexstr2byte:=hexchar2byte(hex[1])*multiplier+hexchar2byte(hex[2]);
end;

function byte2hexstr(hex:byte):string;
begin
  byte2hexstr:='';
  if ((hex div 16) >=10) then byte2hexstr:=byte2hexstr+char((hex div 16)+55)
  else byte2hexstr:=byte2hexstr+char((hex div 16)+48);
  if ((hex mod 16) >=10) then byte2hexstr:=byte2hexstr+char((hex mod 16)+55)
  else byte2hexstr:=byte2hexstr+char((hex mod 16)+48);
end;

function byte2binstr(bin:byte;split:boolean):string;
var
    i:integer;
begin
  byte2binstr:='';
  for i:=0 to 7 do
   begin
     byte2binstr:=toString(bin mod 2) + byte2binstr;
     bin:=bin div 2;
     if (i=3) and split then byte2binstr:=' '+byte2binstr;
   end;
end;

// =========================================
//              BARE NAVIGATION
// =========================================
procedure MoveDown(lines:integer);
begin
  if lines>0 then
   begin
     if ((select[level]+lines)<maxdisplay[level]) then
        // normal move
        select[level]:=select[level]+lines
     else
      begin
        // move beyond visible field
        lines:=lines-(maxdisplay[level]-select[level]);
        select[level]:=maxdisplay[level];
       end;
     if (lines>0) and (select[level]=maxdisplay[level]) and (maxdisplay[level]<listlen[level]) then
      begin
      // scroll
        scroll[level]:=scroll[level]+lines;
        if scroll[level] > (listlen[level]-maxdisplay[level]) then scroll[level]:=(listlen[level]-maxdisplay[level]);
      end;
  end;
end;

procedure MoveUp(lines:integer);
begin
  if lines>0 then
   begin
     if ((select[level]-lines)>0) then
       // move
       select[level]:=select[level]-lines
     else
       begin
       // move beyond visible field
        lines:=lines-select[level];
        select[level]:=0;
       end;
     if (lines>0) and (select[level]=0) and (scroll[level]>=0) then
       // scroll
       begin
         scroll[level]:=scroll[level]-lines;
         if scroll[level] < 0 then scroll[level]:=0;
       end;
   end;
end;

procedure MoveRight();
begin
  if level=3 then level:=1
  else level:=level+1;
end;

procedure MoveLeft();
begin
  if level=1 then level:=3
  else level:=level-1;
end;


// =========================================
//                 INPUT / OUTPUT
// =========================================

function openFile(filename:String) : boolean;
var
  status:integer;
begin
  assign(exefile,filename);
  {$I-}
  reset(exefile,1);
  {$I+}
  status:=IOResult;
  if status<>0 then
   begin
    writeln('Error opening "',filename,'".');
    openFile:=false;
   end
  else
   begin
    openFile:=true;
   end;
end; {openFile}

procedure closeFile();
begin
  close(exefile);
  {$I+}
end; {closeFile}

function readByte(address:Integer):byte;
var
  actual:word;
begin
  if filesize(exefile) >= address then
  begin
   seek(exefile,address);
   blockread (exefile,readByte,1,actual);
  end;
end;

// converts big-endian to little-endian
function readBytes(address:integer;nrofbytes:byte):fourbyte;
var i:byte;
begin
  if nrofbytes>4 then nrofbytes:=4;
  if nrofbytes<1 then nrofbytes:=1;
  for i:=0 to 3 do
    readBytes[i]:=0;
  for i:=3 downto (4-nrofbytes) do
   begin
     readBytes[i]:=readByte(address);
     address:=address+1;
   end;
end;

function readBytesInt(address:integer;nrofbytes:byte):integer;
begin
  readBytesInt:=fourbyte2int(readBytes(address,nrofbytes));
end;

procedure writeByte(address:Integer;data:byte);
var
  actual:word;
begin
  seek(exefile,address);
  blockwrite(exefile,data,1,actual);
end;


// converts little-endian to big-endian
procedure writeBytes(address:Integer;data:fourbyte;nrofbytes:byte);
var
  i:byte;
begin
  if nrofbytes>4 then nrofbytes:=4;
  if nrofbytes<1 then nrofbytes:=1;
  for i:=3 downto (4-nrofbytes) do
   begin
     writeByte(address,data[i]);
     address:=address+1;
   end;
end;

procedure writeBytesInt(address,data:Integer;nrofbytes:byte);
begin
  writeBytes(address,int2fourbyte(data),nrofbytes);
end;


// =========================================
//              DATA & OFFSETS
// =========================================

// DATA TYPE CASE - Defines length for each data type. VERY important.
function getDataLen(opttype:integer):integer;
begin
  case opttype of
    -1 : getDataLen:=0;  // Safety value for empty list item.
     0 : getDataLen:=2;  // Boolean (2 byte)
     1 : getDataLen:=1;  // 1-byte
     2 : getDataLen:=2;  // 2-byte
     3 : getDataLen:=4;  // 4-byte (REF)
     4 : getDataLen:=1;  // owner
     5 : getDataLen:=2;  // command
     6 : getDataLen:=2;  // Movement type
     7 : getDataLen:=2;  // weapon type
     8 : getDataLen:=2;  // foundation
     9 : getDataLen:=2;  // construction option
    10 : getDataLen:=4;  // structures (prerequisites)
    11 : getDataLen:=2;  // sounds
    12 : getDataLen:=0;  // bit switch 1/8
    13 : getDataLen:=0;  // bit switch 2/8
    14 : getDataLen:=0;  // bit switch 3/8
    15 : getDataLen:=0;  // bit switch 4/8
    16 : getDataLen:=0;  // bit switch 5/8
    17 : getDataLen:=0;  // bit switch 6/8
    18 : getDataLen:=0;  // bit switch 7/8
    19 : getDataLen:=1;  // bit switch 8/8  ; Bit switches must ALWAYS be used in groups of 8. Last one adds the size.
    20 : getDataLen:=2;  // percentages
    21 : getDataLen:=2;  // music
  end;
end;

function getDataLen(typeindex,optindex:integer):integer;
var
    optslist:lev3lst;
begin
  optslist:=TypeOptsList[typeindex];
  getDataLen:=getDataLen(optslist[optindex].typeid)
end;

function getListLen(typeindex,toindex:integer):integer;
var i:integer;
    optslist:lev3lst;
begin
  optslist:=TypeOptsList[typeindex];
  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
  optslistLen:=OptsListlengths[typeindex];
  typeoptsOffset:=TypeOffset[typeindex][gamever];
  getAddress:=typeoptsOffset + (unitindex*getListLen(typeindex,optslistLen+1)) + getListLen(typeindex,optindex);
end;

// Get offset from reference
function getRefOffset(inputhex:fourbyte):integer;
var multiplier,i:integer;
begin
  getRefOffset:=0;
  multiplier:=1;
  for i:=3 downto 0 do
   begin
     getRefOffset:=getRefOffset+((inputhex[i]-refAddress[gamever][i])*multiplier);
     multiplier:=multiplier*256;
   end;
end;

// Get reference from offset
function getOffsetRef(inputint:integer):fourbyte;
var
    i:integer;
begin
  if inputint<>0 then
   begin
     getOffsetRef[0]:=refAddress[gamever][0];
     inputInt:=inputInt+refAddress[gamever][1]*256*256 + refAddress[gamever][2]*256 + refAddress[gamever][3];
     for i:=3 downto 1 do
      begin
        getOffsetRef[i]:=inputInt mod 256;
        inputInt:=inputInt div 256;
      end;
   end
  else
   for i:=3 downto 0 do
    begin
      getOffsetRef[i]:=0;
    end;
end;

// Reads zero-terminated string
function readString(address:integer):string;
var c:byte;
    i:integer;
begin
  i:=0;
  c:=$FF;
  readString:='';
  if address < filesize(exefile) then
   begin
    while (c<>0) and (i<100) do
     begin
       c:=readByte(address+i);
       if c<>0 then readString:=concat(readString+char(c));
       i:=i+1;
     end;
   end
  else readString:='';
end;

// Gets offset from reference with zero and EOF correction.
function getRefAddress(inputhex:fourbyte):integer;
var
    blank:boolean;
    i:integer;
begin
  getRefAddress:=getRefOffset(inputhex);
  blank:=true;
  for i:=0 to 3 do
    if inputHex[i]<>0 then blank:=false;
  if blank then getRefAddress:=0;
  if (getRefAddress<0) or (getRefAddress> filesize(exefile)) then getRefAddress:=-1;
end;

function getRefAddress(typeindex,unitindex,optindex:integer):integer;
begin
  getRefAddress:=getRefAddress(readBytes(getAddress(typeindex,unitindex,optindex),4));
end;

// =========================================
//             DATA TYPE DISPLAY
// =========================================

function GetTypeDescr(id:integer): descr;
begin
  GetTypeDescr:=typedescr[id];
end;

function GetTypeHelp(id:integer): bigdescr;
begin
  GetTypeHelp:=typehelp[id];
end;


// Reads zero-terminated string defined by a reference
function getRefString(inputHex:fourbyte):string;
var
    blank:boolean;
    i:integer;
    ref:integer;
begin
  ref:=getRefOffset(inputhex);
  i:=0;
  blank:=true;
  for i:=0 to 3 do
    if inputHex[i]<>0 then blank:=false;

  if (ref>0) and not blank then getRefString:=readString(ref)
  else if blank then getRefString:='<no reference>'
  else getRefString:='<invalid ref>';
end;

function getRefString(typeindex,unitindex,optindex:integer):string;
begin
  getRefString:=getRefString(readBytes(getAddress(typeindex,unitindex,optindex),4));
end;

function getBytesString(nrofbytes:byte;inputhex:fourbyte;split:boolean):string;
var first,i:integer;
begin
  getBytesString:='';
  if nrofbytes<=4 then first:=4-nrofbytes
  else first:=3;
  for i:=first to 3 do
   begin
     getBytesString:=getBytesString + byte2hexStr(inputhex[i]);
     if (i<>3) and split then getBytesString:=getBytesString + ' ';
   end;
end;

function getBytesString(typeindex,unitindex,optindex:integer;split:boolean):string;
begin
  getBytesString:=getBytesString(maxint(1,getDataLen(typeindex,optindex)),readBytes(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex)),split);
end;

function getBinString(nrofbytes:byte;inputhex:fourbyte;split:boolean):string;
var last,i:integer;
begin
  getBinString:='';
  if nrofbytes<=4 then last:=4-nrofbytes
  else last:=3;
  for i:=last to 3 do
   begin
     getBinString:=getBinString + byte2binStr(inputhex[i],split);
     if (i<>3) and split then getBinString:=getBinString + ' | ';
   end;
end;

function getBinString(typeindex,unitindex,optindex:integer;split:boolean):string;
begin
  getBinString:=getBinString(maxint(1,getDataLen(typeindex,optindex)),readBytes(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex)),split);
end;

{
//unsigned
function getByte(typeindex,unitindex,optindex:integer):byte;
begin
  getByte:=readByte(getAddress(typeindex,unitindex,optindex));
end;
}

//signed
function getBytes(typeindex,unitindex,optindex,nrofbytes:integer):integer;
var i,corrvalue:integer;
begin
  getBytes:=readBytesInt(getAddress(typeindex,unitindex,optindex),nrofbytes);
  corrvalue:=$7F;
  for i:=2 to nrofbytes do
    corrvalue:=corrvalue*$100+$FF;
  if (getBytes>corrvalue) then getBytes:=getBytes-((corrvalue+1)*2);
end;


function getMovementTypeStr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=getBytes(typeindex,unitindex,optindex,2);
  if (i>=0) and (i<=5) then
    getMovementTypestr:=movementTypes[i]
  else getMovementTypestr:='<Invalid type>';
end;

function getSoundStr(typeindex,unitindex,optindex:integer):string;
var
  data,i:integer;
begin
  data:=getBytes(typeindex,unitindex,optindex,2);
  getSoundstr:='<Unknown>';
  for i:=0 to high(sounds) do
   if data=sounds[i].val then getSoundstr:=sounds[i].txt;
  getSoundstr:=Copy(getSoundstr,1,19);
end;

function getMusicStr(typeindex,unitindex,optindex:integer):string;
var
  data,i:integer;
begin
  data:=getBytes(typeindex,unitindex,optindex,2);
  getMusicStr:='<Unknown>';
  for i:=0 to high(music) do
   if data=music[i].val then getMusicStr:=music[i].txt;
  getMusicStr:=Copy(getMusicStr,1,19);
end;

function getPercentageStr(typeindex,unitindex,optindex:integer):string;
var
  data,i:integer;
begin
  data:=getBytes(typeindex,unitindex,optindex,2);
  getPercentageStr:=toString(data) + '/256';
  for i:=0 to high(percentages) do
   if data=percentages[i].val then getPercentageStr:=percentages[i].txt;
end;


function getCommandStr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=getBytes(typeindex,unitindex,optindex,2);
  if (i>=0) and (i<=13) then
    getCommandstr:=commands[i]
  else getCommandstr:='<Invalid type>';
end;

function getFoundationStr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=getBytes(typeindex,unitindex,optindex,2);
  if (i>=0) and (i<=6) then
    getFoundationstr:=foundations[i]
  else getFoundationstr:='<Invalid type>';
end;

function UnitType(i:integer):string;
begin
  if i=-1 then UnitType:='None'
  else UnitType:=getRefString(0,i,1);
end;

function StructureType(i:integer):string;
begin
  if i=-1 then StructureType:='None'
  else StructureType:=getRefString(1,i,1);
end;

function HouseType(i:integer):string;
begin
  if i=-1 then HouseType:='None'
  else HouseType:=getRefString(2,i,0);
end;

function getUnitTypestr(typeindex,unitindex,optindex:integer):string;
var
  i:integer;
begin
  i:=getBytes(typeindex,unitindex,optindex,2);
  getUnitTypestr:=UnitType(i);
end;

function readPrerequisites(len,typeindex,unitindex,optindex:integer):boollist;
var
    val,i,j:integer;
begin
  for i:=0 to 32 do readPrerequisites[i]:=false;
  val:=0;
  for i:=0 to len-1 do
   begin
     val:=readBytesInt(getAddress(typeindex,unitindex,optindex)+i,2);
     for j:=0 to 7 do
      begin
        if (val mod 2) = 1 then readPrerequisites[i*8+j]:=true;
        val:=val div 2;
      end;
   end;
end;

procedure writePrerequisites(len:integer;p:boollist;typeindex,unitindex,optindex:integer);
var
    modifier,i,j:integer;
    val:byte;
begin
  val:=0;
  for i:=0 to len-1 do
   begin
     modifier:=1;
     val:=0;
     for j:=0 to 7 do
      begin
        if p[i*8+j] then val:=val+modifier;
        modifier:=modifier*2;
      end;
     writeByte(getAddress(typeindex,unitindex,optindex)+i,val);
   end;
end;

function getPrerequisitestr(len,typeindex,unitindex,optindex:integer):string;
var
    b:boollist;
    i:integer;
begin
  b:=readPrerequisites(len,typeindex,unitindex,optindex);
  getPrerequisitestr:='';
  for i:=0 to 18 do
    if b[i] then getPrerequisitestr:=getPrerequisitestr + smartAbbrev(StructureType(i)) + ',';
  if length(getPrerequisitestr)>0 then getPrerequisitestr:=copy(getPrerequisitestr,1,length(getPrerequisitestr)-1);
  if length(getPrerequisitestr)>19 then getPrerequisitestr:=concat(copy(getPrerequisitestr,1,17)+'..')
  else 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(HouseType(i),1,2) + ',';
  if length(getOwnerstr)>0 then getOwnerstr:=copy(getOwnerstr,1,length(getOwnerstr)-1)
  else getOwnerstr:='(none)';
end;

function readSwitchBoolean(typeindex,unitindex,optindex,bitindex:integer):boolean;
var
    val:byte;
begin
    val:=readBytesInt(getAddress(typeindex,unitindex,optindex),1);
    readSwitchBoolean:=((val and (byte(1) shl bitindex)) > 0);
end;

procedure writeSwitchBoolean(value:boolean;typeindex,unitindex,optindex,bitindex:integer);
var
    val:byte;
begin
    val:=readBytesInt(getAddress(typeindex,unitindex,optindex),1);
    if value then val := val or      (word(1) shl bitindex)
    else          val := val and not (word(1) shl bitindex);
    writeByte(getAddress(typeindex,unitindex,optindex),val);
end;

function getSwitchBooleanstr(typeindex,unitindex,optindex,bitindex:integer):string;
var
    val:boolean;
begin
  val:=readSwitchBoolean(typeindex,unitindex,optindex,bitindex);
  if not HexDisplay then getSwitchBooleanstr:=booleans[word(val)]
  else getSwitchBooleanstr:=(toString(word(val)));
end;


function getBooleanstr(typeindex,unitindex,optindex:integer):string;
var
    i:integer;
begin
  i:=readByte(getAddress(typeindex,unitindex,optindex));
  if (i=0) or (i=1) then getBooleanstr:=booleans[i]
  else getBooleanstr:='<Invalid>';
end;

// DATA TYPE CASE - gets correct strings to display in second column of 3rd list
function getDataStr(typeindex,unitindex,optindex:integer):string;
begin
  getDataStr:='';
  case lev3list[optindex].typeid of
     0 : getDataStr:=getBooleanstr(typeindex,unitindex,optindex);                 // boolean
     1 : getDataStr:=tostring(getBytes(typeindex,unitindex,optindex,1));          // 1-byte
     2 : getDataStr:=toString(getBytes(typeindex,unitindex,optindex,2));          // 2-byte
     3 : getDataStr:=getRefString(typeindex,unitindex,optindex);                  // 4-byte (REF)
     4 : getDataStr:=getOwnerstr(typeindex,unitindex,optindex);                   // owner
     5 : getDataStr:=getCommandStr(typeindex,unitindex,optindex);                 // command
     6 : getDataStr:=getMovementTypestr(typeindex,unitindex,optindex);            // movement type
     7 : getDataStr:=getUnitTypestr(typeindex,unitindex,optindex);                // weapon type
     8 : getDataStr:=getFoundationStr(typeindex,unitindex,optindex);              // foundation
     9 : getDataStr:=getUnitTypestr(typeindex,unitindex,optindex);                // construction option
    10 : getDataStr:=getPrerequisitestr(4,typeindex,unitindex,optindex);          // structures
    11 : getDataStr:=getSoundStr(typeindex,unitindex,optindex);                   // sounds
    12 : getDataStr:=getSwitchBooleanstr(typeindex,unitindex,optindex,0);         // bit switch 1/8
    13 : getDataStr:=getSwitchBooleanstr(typeindex,unitindex,optindex,1);         // bit switch 2/8
    14 : getDataStr:=getSwitchBooleanstr(typeindex,unitindex,optindex,2);         // bit switch 3/8
    15 : getDataStr:=getSwitchBooleanstr(typeindex,unitindex,optindex,3);         // bit switch 4/8
    16 : getDataStr:=getSwitchBooleanstr(typeindex,unitindex,optindex,4);         // bit switch 5/8
    17 : getDataStr:=getSwitchBooleanstr(typeindex,unitindex,optindex,5);         // bit switch 6/8
    18 : getDataStr:=getSwitchBooleanstr(typeindex,unitindex,optindex,6);         // bit switch 7/8
    19 : getDataStr:=getSwitchBooleanstr(typeindex,unitindex,optindex,7);         // bit switch 8/8
    20 : getDataStr:=getPercentageStr(typeindex,unitindex,optindex);              // percentage
    21 : getDataStr:=getMusicStr(typeindex,unitindex,optindex);                   // music
  end;
end;


function inputValue(inputstr:String;minval,maxval:integer):integer;
var charin: char;
    minlen,maxlen:integer;
    tempstr:string;
begin
   charin:=char(0);
   str(minval,tempstr);
   minlen:=Length(tempstr);
   str(maxval,tempstr);
   maxlen:=Length(tempstr);
   write(inputstr);
   while ((charin<>keymap.esc) and (charin<>keymap.F2) and (charin<>keymap.F3) and (charin<>keymap.F4) and (charin<>keymap.enter)) do
   begin
    charin:=ReadKey();
    if ((byte(charin)=45) and (Length(inputstr)=0) and (minval<0))
        or ((byte(charin)>=48) and (byte(charin)<=57)
          and (    ((Length(inputstr)<minlen) and (Copy(inputstr,1,1)='-'))
                or ((Length(inputstr)<maxlen) and (Copy(inputstr,1,1)<>'-'))
              )
       ) then
     begin
       inputstr:=inputstr+charin;
       write(charin);
     end
    else if (charin=keymap.back) and (Length(inputstr)>0) then
     begin
       inputstr:=Copy(inputstr, 1, (Length(inputstr)-1));
       write(keymap.back);
       write('_');
       write(keymap.back);
     end;
   end;
   val(inputstr,inputvalue);
   errormsg:='';
   if ((charin=keymap.esc) or (charin=keymap.F2) or (charin=keymap.F3) or (charin=keymap.F4)) then errormsg:='Canceled'
   else if (inputvalue>maxval) then errormsg:='Value too large!'
   else if (inputvalue<minval) then errormsg:='Value too small!'
end; {inputValue}

function inputValue(minval,maxval:integer):integer;
begin
  inputValue:=inputValue('',minval,maxval);
end;

function inputHexBytes(inputstr:String;nrofbytes:byte):fourbyte;
var charin: char;
    i:integer;
begin
   for i:=0 to 3 do
     inputHexBytes[i]:=0;
   if nrofbytes>4 then nrofbytes:=4;
   for i:=1 to length(inputstr) do
     begin
      write(inputstr[i]);
      if (i<>0) and (i mod 2=0) and (i<(length(inputstr))) then write(' ');
     end;
   charin:=char(0);
   while ((charin<>keymap.esc) and (charin<>keymap.F2) and (charin<>keymap.F3) and (charin<>keymap.F4) and (charin<>keymap.enter)) do
   begin
    charin:=ReadKey();
    if (byte(charin)>=97) and (byte(charin)<=102) then charin:=char(byte(charin)-32);
    if (((byte(charin)>=48) and (byte(charin)<=57)) or ((byte(charin)>=65) and (byte(charin)<=70)))
       and (length(inputstr)<(nrofbytes*2)) then
     begin
       inputstr:=inputstr+charin;
       write(charin);
       if (length(inputstr)>0) and ((length(inputstr) mod 2)=0) and (length(inputstr)<(nrofbytes*2))then write(' ');
     end
    else if (charin=keymap.back) and (Length(inputstr)>0) then
     begin
       if ((length(inputstr) mod 2)=0) and (length(inputstr)<(nrofbytes*2)) then write(keymap.back);
       inputstr:=Copy(inputstr, 1, (Length(inputstr)-1));
       write(keymap.back);
       write('_');
       write(keymap.back);
     end;
   end;
   while (length(inputstr)<(nrofbytes*2)) do
    begin
      inputstr:=inputstr+'0';
      write('0');
      if (length(inputstr)>0) and ((length(inputstr) mod 2)=0) then write(' ');
    end;
   while (length(inputstr)<(4*2)) do
    inputstr:='0'+inputstr;
   for i:=0 to 3 do
    begin
     inputhexbytes[i]:=hexstr2byte(copy(inputstr,1+i*2,2));
    end;
   if ((charin=keymap.esc) or (charin=keymap.F2) or (charin=keymap.F3) or (charin=keymap.F4)) then errormsg:='Canceled'
   else errormsg:='';
end;

// =========================================
//              GENERAL LAYOUT
// =========================================

procedure TextCol(lev,i:integer);
begin
  if      (i= select[lev]) and (level= lev) then begin TextColor(activeitemcolor);   TextBackground(activeitembgcolor);   end
  else if (i= select[lev]) and (level<>lev) then begin TextColor(inactiveitemcolor); TextBackground(inactiveitembgcolor); end
  else if (i<>select[lev]) and (level= lev) then begin TextColor(activelevcolor);    TextBackground(activelevbgcolor);    end
  else if (i<>select[lev]) and (level<>lev) then begin TextColor(inactivelevcolor);  TextBackground(inactivelevbgcolor);  end;
end;

procedure ResetCol();
begin
  TextColor(activelevcolor);
  TextBackground(activelevbgcolor);
end;

procedure drawLineV(x,y,len:integer; c:char);
var count:integer;
begin
   for count:=0 to (len-1) do
    begin
      gotoxy(x,y+count);
      write(c);
    end;
end;

procedure drawLineH(x,y,len:integer; c:char);
var count:integer;
begin
  gotoxy(x,y);
  for count:=0 to len-1 do write(c);
end;

procedure clearBlock(x,y,w,h:integer);
var count:integer;
begin
  for count:=0 to h-1 do
   begin
     gotoxy(x,y+count);
     drawLineH(x,y+count,w,' ');
   end;
end;

// x-coord, y-coord, width, height, line in middle, draw as active frame, clear inside
procedure drawFrame(x,y,w,h,m:integer; active,fill:boolean );
var charset:integer;
begin
  if active then charset:=2
  else           charset:=1;
  gotoxy(x,y);
  write(char(framechar[charset,1]));
  gotoxy(x+w-1,y);
  write(char(framechar[charset,2]));
  gotoxy(x,y+h-1);
  write(char(framechar[charset,3]));
  gotoxy(x+w-1,y+h-1);
  write(char(framechar[charset,4]));
  drawLineH(x+1,y,w-2,char(framechar[charset,5]));
  drawLineH(x+1,y+h-1,w-2,char(framechar[charset,5]));
  drawlineV(x,y+1,h-2,char(framechar[charset,6]));
  drawlineV(x+w-1,y+1,h-2,char(framechar[charset,6]));
  if fill then clearBlock(x+1,y+1,w-2,h-2);
  if (m>1) then
   begin
     gotoxy(x+m-1,y);
     write(char(framechar[charset,7]));
     gotoxy(x+m-1,y+h-1);
     write(char(framechar[charset,8]));
     drawLineV(x+m-1,y+1,h-2,char(framechar[charset,6]));
   end;
  resetCursor();
end; {drawFrame}

procedure drawHeader();
begin
  gotoxy(1,1);
  drawLineH(1,1,80,char(framechar[2,5]));
  gotoxy(3,1);
  write(' '); write(editorname); write(' '); write(editorversion); write(' ');
  gotoxy(65,1);
  write(' by '); write(editorauthor); write(' ');
end;

procedure drawFooter();
begin
  gotoxy(1,25);
  write('[F1]=HELP  [F2][F3][F4]=EDIT  [F11]=HEX MODE  [F12]=INFO');
  gotoxy(59,25);
  write('[' + gamenamestr + '] - ' + dunever[gamever]);
end;

procedure drawHeaders();
begin
  drawHeader();
  drawFooter();
end;

// =========================================
//             SELECTION DISPLAY
// =========================================

function calcSelectBin(len:integer):integer;
var
 i:integer;
begin
  calcSelectBin:=len;
  for i:=0 to (len) do
   begin
     if (i mod 4 = 0) and (i<>0) then calcSelectBin:=calcSelectBin+1;
     if (i mod 8 = 0) and (i<>0) then calcSelectBin:=calcSelectBin+2;
   end;
end;

procedure drawSelectBin();
var
 i:integer;
begin
  gotoxy(selectBoundaries[4][1]+1,selectBoundaries[4][2]-2);
  for i:=0 to calcSelectBin(listlen[4]) do write(' ');
  gotoxy(selectBoundaries[4][1]+1+calcSelectBin(listlen[4])-calcSelectBin(select[4]+scroll[4]),selectBoundaries[4][2]-2);
  write(char(25));
  gotoxy(selectBoundaries[4][1]+1,selectBoundaries[4][2]);
  for i:=0 to calcSelectBin(listlen[4]) do write(' ');
  gotoxy(selectBoundaries[4][1]+1+calcSelectBin(listlen[4])-calcSelectBin(select[4]+scroll[4]),selectBoundaries[4][2]);
  write(char(24));
  resetCursor();
end;

procedure drawSel(l,pos:integer);
begin
  if maxdisplay[l] >=0 then
   begin
     TextCol(l,pos);
     case l of
       1 : drawLineH(selectBoundaries[l][1],selectBoundaries[l][2]+pos,selectionLengths[l],' ');
       2 : drawLineH(selectBoundaries[l][1],selectBoundaries[l][2]+pos,selectionLengths[l],' ');
       3 : drawLineH(selectBoundaries[l][1],selectBoundaries[l][2]+pos,selectionLengths[l],' ');
       4 : drawLineH(selectBoundaries[l][1],selectBoundaries[l][2]+pos,selectionLengths[l],' ');
     end;
     gotoxy(selectBoundaries[l][1]+1,selectBoundaries[l][2]+pos);
     case l of
       1 : write(mainlist[(pos+scroll[l])]);
       2 : write(lev2List[(pos+scroll[l])]);
       3 : begin
             write(lev3List[(pos+scroll[l])].text);
             gotoxy(selectBoundaries[3][1]+secondcolumn[3]-1,selectBoundaries[3][2]+pos);
             drawLineH(selectBoundaries[3][1]+secondcolumn[3]-1,selectBoundaries[3][2]+pos,secondcolumnSelection,' '); // second line
             gotoxy(selectBoundaries[3][1]+secondcolumn[3],selectBoundaries[3][2]+pos);
             write(lev3data[pos]);
           end;
       4 : if (listlen[4]>=0) then write(lev4List[(pos+scroll[l])]);
     end;
     ResetCol();
     resetCursor();
   end;
end;

procedure drawSelect(l,pos:integer);
begin
 if binEd and (level=4) then drawSelectBin()
 else drawSel(l,pos);
end;

procedure drawSelect(lev:integer);
begin
 if binEd and (level=4) then drawSelectBin()
 else drawSel(lev,select[lev]);
end;

procedure drawSelect();
begin
 if binEd and (level=4) then drawSelectBin()
 else drawSel(level,select[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);
  listlen[4]:=l;
  for i:=0 to listlen[4] do
   begin
     if o[i] then lev4List[i]:='+ ' + uppercase(StructureType(i))
     else lev4List[i]:='  ' + StructureType(i);
   end;
end;

procedure MakeOwnersList(typeindex,unitindex,optindex:integer);
var
    i:integer;
    o:boollist;
begin
  o:=readOwner(typeindex,unitindex,optindex);
  listlen[4]:=5;
  for i:=0 to listlen[4] do
   begin
     if o[i] then lev4List[i]:='+ ' + uppercase(HouseType(i))
     else lev4List[i]:='  ' + HouseType(i);
   end;
end;

procedure makeMovementTypesList(typeindex,unitindex,optindex:integer);
var
    i:integer;
    b:byte;
begin
  b:=getBytes(typeindex,unitindex,optindex,2);
  listlen[4]:=5;
  for i:=0 to listlen[4] do
   begin
     if (i=b) then lev4List[i]:='* ' + uppercase(MovementTypes[i])
     else lev4List[i]:='  ' + MovementTypes[i];
   end;
end;

procedure makeSoundsList(typeindex,unitindex,optindex:integer);
var
    val,i:integer;
begin
  val:=getBytes(typeindex,unitindex,optindex,2);
  listlen[4]:=high(sounds);
  for i:=0 to listlen[4] do
   begin
     if val=sounds[i].val then lev4List[i]:='* ' + uppercase(sounds[i].txt)
     else lev4List[i]:='  ' + sounds[i].txt;
   end;
end;

procedure makeMusicList(typeindex,unitindex,optindex:integer);
var
    val,i:integer;
begin
  val:=getBytes(typeindex,unitindex,optindex,2);
  listlen[4]:=high(music);
  for i:=0 to listlen[4] do
   begin
     if val=music[i].val then lev4List[i]:='* ' + uppercase(music[i].txt)
     else lev4List[i]:='  ' + music[i].txt;
   end;
end;

procedure makePercentagesList(typeindex,unitindex,optindex:integer);
var
    val,i:integer;
begin
  val:=getBytes(typeindex,unitindex,optindex,2);
  listlen[4]:=high(percentages);
  for i:=0 to listlen[4] do
   begin
     if val=percentages[i].val then lev4List[i]:='* ' + uppercase(percentages[i].txt)
     else lev4List[i]:='  ' + percentages[i].txt;
   end;
end;


procedure makeCommandsList(typeindex,unitindex,optindex:integer);
var
    val,i:integer;
begin
  val:=getBytes(typeindex,unitindex,optindex,2);
  listlen[4]:=13;
  for i:=0 to listlen[4] do
   begin
     if (i=val) then lev4List[i]:=uppercase(commands[i])
     else lev4List[i]:=commands[i];
   end;
end;

procedure makeFoundationsList(typeindex,unitindex,optindex:integer);
var
    val,i:integer;
begin
  val:=getBytes(typeindex,unitindex,optindex,2);
  listlen[4]:=6;
  for i:=0 to listlen[4] do
   begin
     if (i=val) then lev4List[i]:=' <' + foundations[i] + '>'
     else lev4List[i]:='  ' + foundations[i];
   end;
end;

procedure makeUnitTypeList(typeIndex,unitindex,optindex:integer);
var
    val,i: integer;
begin
  val:=getBytes(typeIndex,unitindex,optindex,2);
  listlen[4]:=typeListLen[0]+1;
  lev4List[0]:='None';
  if (val=-1) then lev4List[0]:='* '+uppercase(lev4List[0])
  else             lev4List[0]:='  '+lev4List[0];
  for i:=0 to listlen[4] do
   begin
     if (i=val) then lev4List[i+1]:='* '+uppercase(UnitType(i))
     else            lev4List[i+1]:='  '+UnitType(i);
   end;
end;

procedure makeSwitchBooleanList(typeindex,unitindex,optindex,bitindex:integer);
var
    b: boolean;
    i: integer;
begin
  b:=readSwitchBoolean(typeindex,unitindex,optindex,bitindex);
  maxdisplay[4]:=1;
  listlen[4]:=1;
  for i:=0 to listlen[4] do
   begin
     if (i=integer(b)) then lev4List[i]:=uppercase(booleans[i])
     else lev4List[i]:=booleans[i];
   end;
end;

procedure makeBooleanList(typeindex,unitindex,optindex:integer);
var
    b,i: integer;
begin
  b:=getBytes(typeindex,unitindex,optindex,1);
  maxdisplay[4]:=1;
  listlen[4]:=1;
  for i:=0 to listlen[4] do
   begin
     if (i=b) then lev4List[i]:=uppercase(booleans[i])
     else lev4List[i]:=booleans[i];
   end;
end;

procedure writeBinList(typeindex,unitindex,optindex:integer);
begin
  gotoxy(selectBoundaries[4][1]+1,selectBoundaries[4][2]-1);
  write(getBinString(typeindex,unitindex,optindex,true));
  gotoxy(selectBoundaries[4][1]+11,selectBoundaries[4][2]+2);
  write(getBytesString(typeindex,unitindex,optindex,true));
end;


procedure drawLev4();
var
    i,last,typeindex,unitindex,optindex:integer;
begin
  typeindex:=select[1]+scroll[1];
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  if not binEd then
   begin
    maxdisplay[4]:=5;
    // DATA TYPE CASE - Creates level 4 list for each data type
    case lev3list[optindex].typeid of
       0 : makeBooleanList(typeindex,unitindex,optindex);            //boolean
       1 : listlen[4]:=-1;                                           //signed byte
       2 : listlen[4]:=-1;                                           //2-byte
       3 : listlen[4]:=-1;                                           //4-byte
       4 : makeOwnersList(typeindex,unitindex,optindex);             //owner
       5 : makeCommandsList(typeindex,unitindex,optindex);           //command
       6 : makeMovementTypesList(typeindex,unitindex,optindex);      //Movement type
       7 : makeUnitTypeList(typeindex,unitindex,optindex);           //weapon type
       8 : makeFoundationslist(typeindex,unitindex,optindex);        //foundation
       9 : makeUnitTypeList(typeindex,unitindex,optindex);           //construction option
      10 : makePrerequisitesList(4,typeindex,unitindex,optindex);    //structures
      11 : makeSoundsList(typeindex,unitindex,optindex);             //sounds
      12 : makeSwitchBooleanList(typeindex,unitindex,optindex,0);    //bit switch 1/8
      13 : makeSwitchBooleanList(typeindex,unitindex,optindex,1);    //bit switch 2/8
      14 : makeSwitchBooleanList(typeindex,unitindex,optindex,2);    //bit switch 3/8
      15 : makeSwitchBooleanList(typeindex,unitindex,optindex,3);    //bit switch 4/8
      16 : makeSwitchBooleanList(typeindex,unitindex,optindex,4);    //bit switch 5/8
      17 : makeSwitchBooleanList(typeindex,unitindex,optindex,5);    //bit switch 6/8
      18 : makeSwitchBooleanList(typeindex,unitindex,optindex,6);    //bit switch 7/8
      19 : makeSwitchBooleanList(typeindex,unitindex,optindex,7);    //bit switch 8/8
      20 : makePercentagesList(typeindex,unitindex,optindex);        //percentages
      21 : makeMusicList(typeindex,unitindex,optindex);              // music
    end;

    // display size
    if listlen[4]<maxdisplay[4] then
    begin
     last:=listlen[4];
     maxdisplay[4]:=listlen[4];
    end
    else last:=maxdisplay[4];
    for i:=0 to last do
     begin
       TextCol(4,i);
       drawLineH(selectBoundaries[4][1],selectBoundaries[4][2]+i,selectionLengths[4],' ');
       gotoxy(selectBoundaries[4][1]+1,selectBoundaries[4][2]+i);
       write(lev4List[(i+scroll[4])]);
     end;
   end
  else writeBinList(typeindex,unitindex,optindex);
  resetCursor();
  ResetCol();
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
    TextCol(3,i);
    if not HexDisplay then lev3data[i]:=getDataStr(select[1]+scroll[1],select[2]+scroll[2],i+scroll[3])
    else
      begin
        if (lev3list[i+scroll[3]].typeid >=12) and (lev3list[i+scroll[3]].typeid <=19) then
          lev3data[i]:=getDataStr(select[1]+scroll[1],select[2]+scroll[2],i+scroll[3])
        else
          lev3data[i]:=getBytesString(select[1]+scroll[1],select[2]+scroll[2],i+scroll[3],true);
      end;
  end;
  for i:=0 to last do
  begin
    TextCol(3,i);
    drawLineH(selectBoundaries[3][1]+secondcolumn[3]-1,selectBoundaries[3][2]+i,secondcolumnSelection,' ');
    gotoxy(selectBoundaries[3][1]+secondcolumn[3],selectBoundaries[3][2]+i);
    write(lev3data[i]);
  end;
  ResetCol();
  if last < (selectBoundaries[3][4]-selectBoundaries[3][2]) then
    for i:=last+1 to (selectBoundaries[3][4]-selectBoundaries[3][2]) do
    begin
      drawLineH(selectBoundaries[3][1]+secondcolumn[3]-1,selectBoundaries[3][2]+i,secondcolumnSelection,' ');
      gotoxy(selectBoundaries[3][1]+1,selectBoundaries[3][2]+i);
    end;
  resetCursor();
end;

procedure drawLev3();
var i,last: integer;
begin
  maxdisplay[3]:=minint((selectBoundaries[3][4]-selectBoundaries[3][2]),listlen[3]);
  if listlen[3]<maxdisplay[3] then last:=listlen[3]
  else last:=maxdisplay[3];
  for i:=0 to last do
  begin
    TextCol(3,i);
    drawLineH(selectBoundaries[3][1],selectBoundaries[3][2]+i,selectionLengths[3],' ');
    gotoxy(selectBoundaries[3][1]+1,selectBoundaries[3][2]+i);
    write(lev3List[(i+scroll[3])].text);
  end;
  ResetCol();
  if last < (selectBoundaries[3][4]-selectBoundaries[3][2]) then
    for i:=last+1 to (selectBoundaries[3][4]-selectBoundaries[3][2]) do
    begin
      drawLineH(selectBoundaries[3][1],selectBoundaries[3][2]+i,selectionLengths[3],' ');
      gotoxy(selectBoundaries[3][1]+1,selectBoundaries[3][2]+i);
    end;
  drawLev3data();
end;

procedure drawLev2();
var i,last: integer;
begin
  maxdisplay[2]:=minint((selectBoundaries[2][4]-selectBoundaries[2][2]),listlen[2]);
  if listlen[2]<maxdisplay[2] then last:=listlen[2]
  else last:=maxdisplay[2];
  for i:=0 to last do
  begin
    TextCol(2,i);
    drawLineH(selectBoundaries[2][1],selectBoundaries[2][2]+i,selectionLengths[2],' ');
    gotoxy(selectBoundaries[2][1]+1,selectBoundaries[2][2]+i);
    write(lev2List[(i+scroll[2])]);
  end;
  ResetCol();
  if last < (selectBoundaries[2][4]-selectBoundaries[2][2]) then
    for i:=last+1 to (selectBoundaries[2][4]-selectBoundaries[2][2]) do
    begin
      drawLineH(selectBoundaries[2][1],selectBoundaries[2][2]+i,selectionLengths[2],' ');
    end;
  resetCursor();
end;

procedure drawLev1();
var i,last: integer;
begin
  maxdisplay[1]:=minint((selectBoundaries[1][4]-selectBoundaries[1][2]),listlen[1]);
  if listlen[1]<maxdisplay[1] then last:=listlen[1]
  else last:=maxdisplay[1];
  for i:=0 to last do
  begin
    TextCol(1,i);
    drawLineH(selectBoundaries[1][1],selectBoundaries[1][2]+i,selectionLengths[1],' ');
    gotoxy(selectBoundaries[1][1]+1,selectBoundaries[1][2]+i);
    write(mainlist[(i+scroll[1])]);
  end;
  ResetCol();
  if last < (selectBoundaries[1][4]-selectBoundaries[1][2]) then
    for i:=last+1 to (selectBoundaries[1][4]-selectBoundaries[1][2]) do
    begin
      drawLineH(selectBoundaries[2][1],selectBoundaries[2][2]+i,selectionLengths[2],' ');
    end;
  resetCursor();
end;


procedure drawList(l:integer);
begin
  case l of
   1 : drawLev1();
   2 : drawLev2();
   3 : drawLev3();
   4 : drawLev4();
  end;
end;

procedure drawList();
begin
   drawList(level);
end;

procedure drawLists();
var i:integer;
begin
   for i:=1 to 3 do drawList(i);
end;

procedure updateLev2Lists();
var listtype,i:integer;
begin
  listtype:=select[1]+scroll[1];
  listlen[3]:=OptsListlengths[listtype];
  lev3List:=TypeOptsList[listtype];
  listlen[2]:=typeListLen[listtype];
  for i:=0 to listlen[2] do
    begin
      lev2List[i]:=getDataStr(listtype,i,refoption[listtype]);
      if HexDisplay then // hagafluub
        begin
          lev2List[i]:=copy(lev2List[i],1,13);
          while length(lev2List[i]) < 13 do lev2List[i]:=lev2List[i] + ' ';
          if (lev3list[select[3]+scroll[3]].typeid >=12) and (lev3list[select[3]+scroll[3]].typeid <=19) then
              lev2List[i]:=lev2List[i] + getDataStr(select[1]+scroll[1],i,select[3]+scroll[3])
          else
              lev2List[i]:=lev2List[i] + getBytesString(select[1]+scroll[1],i,select[3]+scroll[3],false);
        end;
    end;
end;

procedure updateLists();
var i: integer;
begin
  for i:=2 to 3 do
    begin
      scroll[i]:=0;
      select[i]:=0;
    end;
  updateLev2Lists();
  for i:=2 to 3 do
    begin
      drawList(i);
      drawSelect(i);
    end;
end;

// =========================================
//                INPUT LAYOUT
// =========================================

procedure showinputInt(typeindex,unitindex,optindex,minval,maxval:integer);
var
    input:integer;
    curval:String;
    typeinfo:descr;
begin
   typeinfo:=GetTypeDescr(lev3list[optindex].typeid);
   maxdisplay[4]:=0;
   listlen[4]:=-1;
   scroll[4]:=0;
   select[4]:=0;
   if (typeinfo[2])<>'' then
        gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-2)
   else gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-3);
   write('Old value : ');
   curval:=toString(getBytes(typeindex,unitindex,optindex,getDataLen(typeindex,optindex)));
   write(curval);
   gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
   write('___________________');
   gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
   input:=inputValue(curval,minval,maxval);
   gotoxy(selectBoundaries[4][1]+32,selectBoundaries[4][2]);
   if length(errormsg)=0 then
    begin
      // SAVE
      writeBytesInt(getAddress(typeindex,unitindex,optindex),input,getDataLen(lev3list[optindex].typeid));
    end
   else if errormsg<> 'Canceled' then
    begin
      write(errormsg);
      cntin();
    end;
   resetCursor();
   level:=prevlev;
end;

procedure showinputInt(typeindex,unitindex,optindex:integer;signed:boolean);
var i,minval,maxval:integer;
begin
  minval:=-128;
  maxval:=0;
  for i:=2 to getDataLen(typeindex,optindex) do
    minval:=minval*256;
  if signed then maxval:=(minval+1)*-1
  else
   begin
     maxval:=((minval+1)*-2)+1;
     minval:=0;
   end;
  showinputInt(typeindex,unitindex,optindex,minval,maxval);
end;

procedure showinputInt2(typeindex,unitindex,optindex:integer);
var i,minval,maxval:integer;
begin
  minval:=-128;
  maxval:=0;
  for i:=2 to (maxInt(1,getDataLen(typeindex,optindex))) do
    minval:=minval*256;
  maxval:=(minval+1)*-1;
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-4);
  write('Data range: ');
  write(minval,',',maxval);
  showinputInt(typeindex,unitindex,optindex,minval,maxval);
end;


procedure showinputOffset(typeindex,unitindex,optindex:integer);
var
    input:integer;
    curval:integer;
    ref:fourbyte;
    typeinfo:descr;
begin
  typeinfo:=GetTypeDescr(lev3list[optindex].typeid);
  maxdisplay[4]:=0;
  listlen[4]:=-1;
  scroll[4]:=0;
  select[4]:=0;
  if (typeinfo[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('___________________');
  gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
  input:=inputValue(toString(curval),0,filesize(exefile)-1);
  gotoxy(selectBoundaries[4][1]+32,selectBoundaries[4][2]);
  if length(errormsg)=0 then
   begin
     // SAVE
     ref:=getOffsetRef(input);
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+2);
     write('                                                 ');
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+2);
     write('New value : ');
     write(getBytesString(4,ref,true));
     if input<>0 then begin write(' (offset '); write(getRefAddress(ref)); write(')'); end
     else write(' (blank)');
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+3);
     write('                                                 ');
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+3);
     write('New string: ');
     if input<>0 then write('"');
     write(getRefString(ref));
     if input<>0 then write('"');
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+5);
     write('[ENTER]/[SPACE]: save     [ESC]: cancel');
     keyread:=ReadKey();
     if (keyread=keymap.ENTER) or (keyread=keymap.ENTER) then
       writeBytes(getAddress(typeindex,unitindex,optindex),ref,4);
   end
  else if errormsg<> 'Canceled' then
   begin
     write(errormsg);
     cntin();
   end;
  resetCursor();
  level:=prevlev;
end;

procedure showinputHex(typeindex,unitindex,optindex:integer);
var
    input:fourbyte;
begin
  maxdisplay[4]:=0;
  listlen[4]:=-1;
  scroll[4]:=0;
  select[4]:=0;
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-4);
  write('Old value : ' + getBytesString(typeindex,unitindex,optindex,true));
  gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
  input:=inputHexBytes(getBytesString(typeindex,unitindex,optindex,false),maxint(1,getDataLen(typeindex,optindex)));
  gotoxy(selectBoundaries[4][1]+32,selectBoundaries[4][2]);
  if length(errormsg)=0 then
    writeBytes(getAddress(typeindex,unitindex,optindex),input,getDataLen(typeindex,optindex));
  ResetCol();
  resetCursor();
  level:=prevlev;
end;


procedure showinputBin(typeindex,unitindex,optindex:integer);
begin
  binEd:=true;
  listlen[4]:=maxint(1,getDataLen(typeindex,optindex))*8-1;
  maxdisplay[4]:=listlen[4];
    scroll[4]:=0;
  // SPECIAL CODE FOR BIT SWITCH VALUES
  if (lev3list[optindex].typeid >=12) and (lev3list[optindex].typeid <=19) then
    select[4]:=lev3list[optindex].typeid-12
  else
    select[4]:=0;
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-4);
  write('Old value : ' + getBytesString(typeindex,unitindex,optindex,true));
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]+2);
  write('New value:');
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-2);
  writeBinList(typeindex,unitindex,optindex);
  drawSelectBin();
end;


procedure showBasicList(typeindex,unitindex,optindex:integer);
var
    val:integer;
begin
  val:=readBytesInt(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex));
  if getDataStr(typeindex,unitindex,optindex) <> '<Invalid type>' then moveDown(val);
  drawLev4();
end;

procedure showSwitchBooleanList(typeindex,unitindex,optindex,bitindex:integer);
var
    val:boolean;
begin
  val:=readSwitchBoolean(typeindex,unitindex,optindex,bitindex);
  if val then moveDown(1);
  drawLev4();
end;

procedure showSoundsList(typeindex,unitindex,optindex:integer);
var
    scrolling,i,val:integer;
begin
  val:=readBytesInt(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex));
  scrolling:=-1;
  for i:=0 to high(sounds) do
   if val=sounds[i].val then scrolling:=i;
  if (scrolling <> -1) then moveDown(scrolling);
  drawLev4();
end;

procedure showMusicList(typeindex,unitindex,optindex:integer);
var
    scrolling,i,val:integer;
begin
  val:=readBytesInt(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex));
  scrolling:=-1;
  for i:=0 to high(music) do
   if val=music[i].val then scrolling:=i;
  if (scrolling <> -1) then moveDown(scrolling);
  drawLev4();
end;

procedure showPercentagesList(typeindex,unitindex,optindex:integer);
var
    scrolling,i,val:integer;
begin
  val:=readBytesInt(getAddress(typeindex,unitindex,optindex),getDataLen(typeindex,optindex));
  scrolling:=-1;
  for i:=0 to high(percentages) do
   if val=percentages[i].val then scrolling:=i;
  if (scrolling <> -1) then moveDown(scrolling);
  drawLev4();
end;


procedure showUnitTypeList(typeindex,unitindex,optindex:integer);
var
    i:integer;
begin
  i:=1+getBytes(typeindex,unitindex,optindex,2);
  if i<>-1 then moveDown(i);
  drawLev4();
end;

// =========================================
//                MAIN LAYOUT
// =========================================

procedure drawLevFrame(lvl:integer;active:boolean);
var
    clear:boolean;
begin
  TextBackground(activelevbgcolor);
  if lvl=4 then clear:=true
  else clear:=false;
  if level=lvl then
    TextColor(activelevcolor)
  else
    TextColor(inactivelevcolor);
  drawFrame(drawBoundaries[lvl][1],drawBoundaries[lvl][2],drawBoundaries[lvl][3],drawBoundaries[lvl][4],secondcolumn[lvl],active,clear);
  TextColor(activelevcolor);
end;

procedure drawLevFrame(lvl:integer);
begin
  case lvl of
   1 : drawLevFrame(lvl,level=1);
   2 : drawLevFrame(lvl,level=2);
   3 : drawLevFrame(lvl,level=3);
   4 : drawLevFrame(lvl,true);
  end;
end;

procedure drawLevFrame();
begin
  drawLevFrame(level);
end;

procedure drawLevFrames();
var i: integer;
begin
  for i:=1 to 3 do
   begin
     drawLevFrame(i);
   end;
end;


procedure clearWorkArea();
var i: integer;
begin
  for i:=3 to 24 do
   begin
     gotoxy(1,i);
     clreol();
   end;
end;

procedure drawLayout4(edtype:integer);
var
    typeindex,unitindex,optindex:integer;
    typeinfo:descr;
begin
  typeindex:=select[1]+scroll[1];
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  typeinfo:=GetTypeDescr(lev3list[optindex].typeid);
  drawLevFrame(prevlev,true);
  drawList(prevlev);
  drawSelect(prevlev);
  drawLevFrame(4);
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-7);
  write('[');
  if edtype=1 then write('BIN ');
  if edtype=2 then write('HEX ');
  if edtype=3 then write('DEC ');
  write('EDIT]');
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-6);
  write('Section   : ');
  write(getDataStr(select[1]+scroll[1],select[2]+scroll[2],refoption[select[1]+scroll[1]]));
  gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-5);
  write('Option    : ');
  write(lev3list[optindex].text);
  if (edtype=0) then
   begin
     gotoxy(selectBoundaries[4][1],selectBoundaries[4][2]-4);
     write('Data type : ');
     gotoxy(selectBoundaries[4][1]+12,selectBoundaries[4][2]-4);
     write(typeinfo[1]);
     gotoxy(selectBoundaries[4][1]+12,selectBoundaries[4][2]-3);
     write(typeinfo[2]);
     gotoxy(17,10);
     maxdisplay[4]:=0;
     listlen[4]:=0;
     select[4]:=0;
     scroll[4]:=0;
     drawList();
     // DATA TYPE CASE - Determines the way each data type is edited
     case lev3list[optindex].typeid of
       0 : showBasicList(typeindex,unitindex,optindex);           //boolean
       1 : showinputInt(typeindex,unitindex,optindex,true);       //signed byte
       2 : showinputInt(typeindex,unitindex,optindex,true);       //2-byte
       3 : showinputOffset(typeindex,unitindex,optindex);         //4-byte
       4 : ; {no special show instructions needed}                //owner
       5 : showBasicList(typeindex,unitindex,optindex);           //command
       6 : showBasicList(typeindex,unitindex,optindex);           //Movement type
       7 : showUnitTypeList(typeindex,unitindex,optindex);        //weapon type
       8 : showBasicList(typeindex,unitindex,optindex);           //foundation
       9 : showUnitTypeList(typeindex,unitindex,optindex);        //construction option
      10 : ; {no special show instructions needed}                //structures
      11 : showSoundsList(typeindex,unitindex,optindex);          //sounds
      12 : showSwitchBooleanList(typeindex,unitindex,optindex,0); //bit switch 1/8
      13 : showSwitchBooleanList(typeindex,unitindex,optindex,1); //bit switch 2/8
      14 : showSwitchBooleanList(typeindex,unitindex,optindex,2); //bit switch 3/8
      15 : showSwitchBooleanList(typeindex,unitindex,optindex,3); //bit switch 4/8
      16 : showSwitchBooleanList(typeindex,unitindex,optindex,4); //bit switch 5/8
      17 : showSwitchBooleanList(typeindex,unitindex,optindex,5); //bit switch 6/8
      18 : showSwitchBooleanList(typeindex,unitindex,optindex,6); //bit switch 7/8
      19 : showSwitchBooleanList(typeindex,unitindex,optindex,7); //bit switch 8/8
      20 : showPercentagesList(typeindex,unitindex,optindex);     //percentages
      21 : showMusicList(typeindex,unitindex,optindex);           //music
     end;
   end
  else if edtype=1 then showinputBin(typeindex,unitindex,optindex)
  else if edtype=2 then showinputHex(typeindex,unitindex,optindex)
  else if edtype=3 then showinputInt2(typeindex,unitindex,optindex);
  if level<>4 then
   begin
     ClearWorkArea();
     drawLevFrames();
     drawLists();
     drawSelects();
   end;
end;

procedure drawLayout();
begin
   if level=4 then drawLayout4(0)
   else drawLevFrames();
end;

procedure FillWorkArea();
begin
      ClearWorkArea();
      drawLayout();
      drawLists();
      drawSelects();
end;

// =========================================
//              LEVEL 4 CONTROLS
// =========================================

procedure startlev4();
begin
  prevlev:=level;
  level:=4;
  if not HexDisplay then drawLayout()
  else
    begin
      if (lev3list[select[3]+scroll[3]].typeid >=12) and (lev3list[select[3]+scroll[3]].typeid <=19) then
        drawLayout4(1)
      else
        drawLayout4(2);
    end;
end;

procedure startlev4bin();
begin
  prevlev:=level;
  level:=4;
  drawLayout4(1);
end;

procedure startlev4hex();
begin
  prevlev:=level;
  level:=4;
  drawLayout4(2);
end;

procedure startlev4dec();
begin
  prevlev:=level;
  level:=4;
  drawLayout4(3);
end;

procedure exitlev4();
begin
  select[4]:=0;
  scroll[4]:=0;
  level:=prevlev;
  binEd:=false;
  FillWorkArea();
end;

// =========================================
//              OUTPUT PROCEDURES
// =========================================

procedure savePrerequisites(len,typeindex,unitindex,optindex:integer);
var
 o:boollist;
 listpos:integer;
begin
  if keyread=keymap.SPACE then
   begin
     o:=readPrerequisites(len,typeindex,unitindex,optindex);
     listpos:=select[4]+scroll[4];
     o[listpos]:=(not o[listpos]);
     writePrerequisites(len,o,typeindex,unitindex,optindex);
     drawLev4();
   end
  else if keyread=keymap.ENTER then exitlev4()
end;

procedure saveOwners(typeindex,unitindex,optindex:integer);
var
 o:boollist;
 listpos:integer;
begin
  if keyread=keymap.SPACE then
   begin
     o:=readOwner(typeindex,unitindex,optindex);
     listpos:=select[4]+scroll[4];
     o[listpos]:=(not o[listpos]);
     writeOwners(o,typeindex,unitindex,optindex);
     drawLev4();
   end
  else if keyread=keymap.ENTER then exitlev4()
end;

procedure saveBasicList(typeindex,unitindex,optindex:integer;hasblank:boolean);
var
  value:integer;
begin
  value:=select[4]+scroll[4];
  if hasblank then value:=value-1;
  writeBytesInt(getAddress(typeindex,unitindex,optindex),value,getDataLen(lev3list[optindex].typeid));
  exitlev4();
end;

procedure saveSoundsList(typeindex,unitindex,optindex:integer);
var
  value:integer;
begin
  value:=sounds[(select[4]+scroll[4])].val;
  writeBytesInt(getAddress(typeindex,unitindex,optindex),value,getDataLen(lev3list[optindex].typeid));
  exitlev4();
end;

procedure saveMusicList(typeindex,unitindex,optindex:integer);
var
  value:integer;
begin
  value:=music[(select[4]+scroll[4])].val;
  writeBytesInt(getAddress(typeindex,unitindex,optindex),value,getDataLen(lev3list[optindex].typeid));
  exitlev4();
end;

procedure savePercentagesList(typeindex,unitindex,optindex:integer);
var
  value:integer;
begin
  value:=percentages[(select[4]+scroll[4])].val;
  writeBytesInt(getAddress(typeindex,unitindex,optindex),value,getDataLen(lev3list[optindex].typeid));
  exitlev4();
end;

procedure saveBitSwitchList(typeindex,unitindex,optindex,bitindex:integer);
var
  value:integer;
begin
  value:=select[4]+scroll[4];
  writeSwitchBoolean((value=1),typeindex,unitindex,optindex,bitindex);
  exitlev4();
end;

procedure inputLev4();
var
    typeindex,unitindex,optindex:integer;
begin
  typeindex:=select[1]+scroll[1];
  unitindex:=select[2]+scroll[2];
  optindex:=select[3]+scroll[3];
  if binEd then savePrerequisites(maxInt(1,getDataLen(typeindex,optindex)),typeindex,unitindex,optindex)
  else
  // DATA TYPE CASE - Determines which procedure to call to save each data type
   case lev3list[select[3]+scroll[3]].typeid of
      0 : saveBasicList(typeindex,unitindex,optindex,false);      //Boolean
      1 : ;                                                       //signed byte
      2 : ;                                                       //2-byte
      3 : ;                                                       //4-byte
      4 : saveOwners(typeindex,unitindex,optindex);               //owner
      5 : saveBasicList(typeindex,unitindex,optindex,false);      //command
      6 : saveBasicList(typeindex,unitindex,optindex,false);      //Movement type
      7 : saveBasicList(typeindex,unitindex,optindex,true);       //weapon type
      8 : saveBasicList(typeindex,unitindex,optindex,false);      //foundation
      9 : saveBasicList(typeindex,unitindex,optindex,true);       //construction option
     10 : savePrerequisites(4,typeindex,unitindex,optindex);      //structures
     11 : saveSoundsList(typeindex,unitindex,optindex);           //sounds
     12 : saveBitSwitchList(typeindex,unitindex,optindex,0);       //bit switch 1/8
     13 : saveBitSwitchList(typeindex,unitindex,optindex,1);       //bit switch 2/8
     14 : saveBitSwitchList(typeindex,unitindex,optindex,2);       //bit switch 3/8
     15 : saveBitSwitchList(typeindex,unitindex,optindex,3);       //bit switch 4/8
     16 : saveBitSwitchList(typeindex,unitindex,optindex,4);       //bit switch 5/8
     17 : saveBitSwitchList(typeindex,unitindex,optindex,5);       //bit switch 6/8
     18 : saveBitSwitchList(typeindex,unitindex,optindex,6);       //bit switch 7/8
     19 : saveBitSwitchList(typeindex,unitindex,optindex,7);       //bit switch 8/8
     20 : savePercentagesList(typeindex,unitindex,optindex);       //percentages
     21 : saveMusicList(typeindex,unitindex,optindex);             //music
   end;

end;

// =========================================
//              HRLP & INFO SCREENS
// =========================================

procedure greyframes();
begin
  prevlev:=level;
  level:=4;
  drawLevFrame(prevlev,true);
  drawList(prevlev);
  drawSelect(prevlev);
  level:=prevlev;
end;

procedure egotrip();
var
   x,y,i:integer;
begin
  greyframes();
  x:=selectBoundaries[4][1];
  y:=selectBoundaries[4][2]-7;
  drawFrame(15,5,52,17,-1,true,true);
  gotoxy(x,y); write('[INFO]');
  y:=y+1; gotoxy(x,y); write(editorname + ' ' + editorversion);
  gotoxy(x+31,y); write('Powered by Pascal');
  y:=y+1; gotoxy(x,y); for i:=1 to length(editorname+editorversion)+1 do write(char(framechar[1][5]));
//           length of line: '________________________________________________'
  y:=y+1; gotoxy(x,y); write('Created by ' + editorauthor + ' <' + editorauthoremail + '>');
  y:=y+1; gotoxy(x,y); write('Project started on February 27, 2008.');
  y:=y+1; gotoxy(x,y); write('Current version: ' + editorversiondate + '.');
  y:=y+1; 
  y:=y+1; gotoxy(x,y); write('Originally requested by MrFlibble, to help him');
  y:=y+1; gotoxy(x,y); write('in his efforts to create the "Super Dune II');
  y:=y+1; gotoxy(x,y); write('Second Edition" mod.');
  y:=y+1; gotoxy(x,y); write('Many thanks to MrFlibble for providing me with');
  y:=y+1; gotoxy(x,y); write('a description of the data structures in Dune II.');
  y:=y+1; 
  y:=y+1; 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;
   help:bigdescr;
begin
  greyframes();
  x:=selectBoundaries[4][1];
  y:=selectBoundaries[4][2]-7;
  if level=3
  // type-specific help
   then drawFrame(15,5,52,12,-1,true,true)
   // general program help
   else drawFrame(15,5,52,18,-1,true,true);
  gotoxy(x,y); write('[HELP]');
  y:=y+1; gotoxy(x,y); write(editorname + ' ' + editorversion);
  y:=y+1; gotoxy(x,y); for i:=1 to length(editorname+editorversion)+1 do write(char(framechar[1][5]));
  if level=3 then
   // type-specific help
   begin
    help:=GetTypeHelp(lev3List[select[3]+scroll[3]].typeid);
    for i:=1 to 7 do
     begin
       y:=y+1;
       gotoxy(x,y);
       write(help[i]);
     end;
   end
  else
   // general program help
   begin
//           allowed length #1: '____________'
//           allowed length #2:    ': ___________________________________'
     y:=y+1; gotoxy(x,y); write('LEFT/RIGHT');
             gotoxy(x+11,y); write(': Navigate between sections');
     y:=y+1; gotoxy(x,y); write('UP/DOWN');
             gotoxy(x+11,y); write(': Navigate through section list');
     y:=y+1; gotoxy(x+14,y); write('Use PGUP/PGDWN/HOME/END for');
     y:=y+1; gotoxy(x+14,y); write('faster scrolling.');
     y:=y+1; 
     y:=y+1; gotoxy(x,y); write('SPACE/ENTER');
             gotoxy(x+11,y); write(': Modify/confirm selected value');
     y:=y+1; gotoxy(x,y); write('F1');
             gotoxy(x+11,y); write(': Help on data type of selected value');
     y:=y+1; gotoxy(x,y); write('F2');
             gotoxy(x+11,y); write(': Edit as bare binary value');
     y:=y+1; gotoxy(x,y); write('F3');
             gotoxy(x+11,y); write(': Edit as bare hexadecimal value');
     y:=y+1; gotoxy(x,y); write('F4');
             gotoxy(x+11,y); write(': Edit as bare decimal value');
     y:=y+1; 
     y:=y+1; gotoxy(x,y); write('F11');
             gotoxy(x+11,y); write(': Toggles full hexadecimal mode');
     y:=y+1; gotoxy(x,y); write('F12');
             gotoxy(x+11,y); write(': Info screen about the editor');
   end;
  resetCursor();
  cntin();
  FillWorkArea();
end;


// =========================================
//                NAVIGATION
// =========================================

procedure MoveVertGen(scr,sel:integer);
begin
  if scr<>scroll[level] then drawList();
  if sel<>select[level] then
   if not BinEd then
    begin
      drawSelect(level,sel);
      drawSelect(level,select[level]);
    end
   else
    drawSelectBin();
  if  (scr<>scroll[level]) or (sel<>select[level]) then
   begin
     if level=1 then updateLists();
     if level=2 then drawLev3data();
     if (level=3) and (HexDisplay) then
       begin
         updateLev2Lists();
         drawLev2();
       end;
     // hagafluub
   end;

end;

procedure MoveHorGen(oldlevel:integer);
begin
  if oldlevel<>level then
   begin
     if not binEd then drawLevFrame(oldlevel);
     drawLevFrame(level);
     if not binEd then drawList(oldlevel);
     drawList(level);
     if not binEd then drawSelect(oldlevel);
     drawSelect(level);
   end;
end;

procedure MoveDown2(lines:integer);
var scr,sel:integer;
begin
  scr:=scroll[level];
  sel:=select[level];
  if not binEd then MoveDown(lines)
  else MoveUp(lines);
  MoveVertGen(scr,sel);
end;

procedure MoveUp2(lines:integer);
var scr,sel:integer;
begin
  scr:=scroll[level];
  sel:=select[level];
  if not binEd then MoveUp(lines)
  else MoveDown(lines);
  MoveVertGen(scr,sel);
end;

procedure MoveRight2();
var oldlevel:integer;
begin
  oldlevel:=level;
  MoveRight();
  MoveHorGen(oldlevel);
end;

procedure MoveLeft2();
var oldlevel:integer;
begin
  oldlevel:=level;
  MoveLeft();
  MoveHorGen(oldlevel);
end;

procedure SwitchDisplay();
begin
  HexDisplay:=not HexDisplay;
  updateLev2Lists();
  drawLev2();
  drawLev3data();
end;

procedure input();
begin
  keyread:=ReadKey();
  if (keyread=keymap.ESC) and (level<>4) then keyread:=char(255)
  else if (level=4) and ((keyread=keymap.ESC) or (keyread=keymap.F2) or (keyread=keymap.F3) or (keyread=keymap.F4)) then exitlev4()
  else if ((level=2) or (level=3)) and ((keyread=keymap.ENTER) or (keyread=keymap.SPACE)) then startlev4()
  else if ((level=2) or (level=3)) and (keyread=keymap.F2) then startlev4bin()
  else if ((level=2) or (level=3)) and (keyread=keymap.F3) then startlev4hex()
  else if ((level=2) or (level=3)) and (keyread=keymap.F4) then startlev4dec()
  else if (keyread=keymap.F11)   and (level<>4) then SwitchDisplay()
  else if (keyread=keymap.RIGHT) and (level<>4) then MoveRight2()
  else if (keyread=keymap.RIGHT) and (level=4) then MoveDown2(1)
  else if (keyread=keymap.LEFT)  and (level<>4) then MoveLeft2()
  else if (keyread=keymap.LEFT)  and (level=4) then MoveUp2(1)
  else if (keyread=keymap.DOWN)  then MoveDown2(1)
  else if (keyread=keymap.UP)    then MoveUp2(1)
  else if (keyread=keymap.PGDN)  then MoveDown2(maxdisplay[level])
  else if (keyread=keymap.PGUP)  then MoveUp2(maxdisplay[level])
  else if (keyread=keymap.ENDK)  then MoveDown2(listlen[level])
  else if (keyread=keymap.HOME)  then MoveUp2(listlen[level])
  else if (keyread=keymap.F1)    and (level<>4) then help()
  else if (keyread=keymap.F12)   and (level<>4) then egotrip()
  else if (level=4) and ((keyread=keymap.SPACE) or (keyread=keymap.ENTER)) then inputlev4()
  else keyread:=char(0);
end;

// =========================================
//               INITIALIZATION
// =========================================



function GetListLength(list:lev3Lst):integer;
var i: integer;
begin
  GetListLength:=-1;
  for i:=0 to high(list) do
    if list[i].typeid<>-1 then GetListLength:=GetListLength+1
    else break;
    gotoxy(1,2);
end;

procedure GetListLengths();
var i: integer;
begin
  for i:=0 to high(TypeOptsList) do
      OptsListlengths[i]:=GetListLength(TypeOptsList[i]);
end;

procedure init();
var i: integer;
begin
  TextBackground(activelevbgcolor);
  BinEd:=false;
  clrScr();
  TextColor(inactivelevcolor);
  drawHeaders();
  TextColor(activelevcolor);
  gotoxy(40,2);
  level:=1;
  GetListLengths();
  listlen[1]:=high(mainlist);
  listlen[2]:=typeListLen[0]+1;
  listlen[3]:=OptsListlengths[0];
  listlen[4]:=0;
  HexDisplay:=false;
  for i:=1 to 4 do
  begin
   select[i]:=0;
   scroll[i]:=0;
  end;
  updateLev2Lists();
  FillWorkArea();
end;

procedure checkver();
var
 i: integer;
 s: string;
begin
  gamever:=-1;
  i:=0;
  while ((gamever=-1) and (i<=high(verAddress))) do
   begin
     s:=readString(verAddress[i]);
     if (uppercase(s) = uppercase(gamenamestr)) then gamever:=i;
     i:=i+1;
   end;
end;

procedure initfilename();
var
 filename:string;
 filenamelen:integer;
begin
  if ParamCount = 0 then
   begin
    gamenamestr:=defnamestr;
    gamepathstr:=defnamestr;
   end
  else
   begin
     filename:=getfilenamefrompath(ParamStr(1));
     filenamelen:=length(filename);
     if ((filenamelen > 0) and (filenamelen <=9 )) then
      begin
        gamepathstr:=ParamStr(1);
        gamenamestr:=uppercase(filename);
      end;
   end;
end;

begin
  clrscr();
  gotoxy(1,3);
  initfilename();
  if (openfile(gamepathstr)) then
   begin
     checkver();
     if gamever<>-1 then
      begin
        init();
        keyread:=char(0);
        while (keyread <> char(255)) do
         begin
           input();
         end;
        closeFile();
        NormVideo;
        clrscr();
        drawHeader();
        gotoxy(1,25);
        gotoxy(1,3);
        writeln('Program exited. Have fun!');
      end
     else
      begin
        closeFile();
        NormVideo;
        clrscr();
        drawHeader();
        gotoxy(1,25);
        gotoxy(1,3);
        writeln('ERROR: Cannot identify the Dune 2 version of the file "', gamenamestr,'".');
        cntin(true);
      end;
   end
  else
   begin
     drawHeader();
     gotoxy(1,4);
     cntin(true);
   end;
end.

