Subversion Repositories oidplus

Rev

Rev 733 | Rev 735 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
733 daniel-mar 1
program OIDPLUS;
2
 
3
(************************************************)
4
(* OIDPLUS.PAS                                  *)
5
(* Author:   Daniel Marschall                   *)
734 daniel-mar 6
(* Revision: 2022-02-14                         *)
733 daniel-mar 7
(* License:  Apache 2.0                         *)
8
(* This file contains:                          *)
9
(* - "OIDplus for DOS" program                  *)
10
(************************************************)
11
 
12
uses
13
  Dos, Crt, StrList, VtsFuncs, VtsCui, OidFile, OidUtils;
14
 
15
const
734 daniel-mar 16
  VERSIONINFO = 'Revision: 2022-02-14';
733 daniel-mar 17
  DEFAULT_STATUSBAR = '(C)2020-2022 ViaThinkSoft. Licensed under the terms of the Apache 2.0 license.';
18
  DISKIO_SOUND_DEBUGGING = false;
19
  DISKIO_SOUND_DELAY = 500;
20
  ASNEDIT_LINES = 10;
21
  DESCEDIT_LINES = 10;
22
  DESCEDIT_PADDING = 3;
23
 
24
procedure _WriteOidFile(filename: string; oid: POid);
25
begin
26
  DrawStatusBar('Write file ' + filename + '...');
27
  WriteOidFile(filename, oid);
28
 
29
  if DISKIO_SOUND_DEBUGGING then
30
  begin
31
    Sound(70);
32
    Delay(DISKIO_SOUND_DELAY - 10);
33
    NoSound;
34
    Delay(10);
35
  end;
36
 
37
  DrawStatusBar(DEFAULT_STATUSBAR);
38
end;
39
 
40
procedure _ReadOidFile(filename: string; oid: POid);
41
begin
42
  DrawStatusBar('Read file ' + filename + '...');
43
  ReadOidFile(filename, oid);
44
 
45
  if DISKIO_SOUND_DEBUGGING then
46
  begin
47
    Sound(50);
48
    Delay(DISKIO_SOUND_DELAY - 10);
49
    NoSound;
50
    Delay(10);
51
  end;
52
 
53
  DrawStatusBar(DEFAULT_STATUSBAR);
54
end;
55
 
56
procedure _Pause;
57
var
58
  bakX, bakY: integer;
59
begin
60
  bakX := WhereX;
61
  bakY := WhereY;
62
  DrawStatusBar('Press any key to continue');
63
  GoToXY(bakX, bakY);
64
  ReadKey;
65
  DrawStatusBar(DEFAULT_STATUSBAR);
66
end;
67
 
68
function _ShowASNIds(subfile: string): string;
69
var
70
  coid: TOID;
71
  j, jmax: integer;
72
  sTmp: string;
73
begin
74
  sTmp := '';
75
  InitOidDef(@coid);
76
  _ReadOidFile(subfile, @coid);
77
  jmax := ListCount(coid.ASNIds)-1;
78
  for j := 0 to jmax do
79
  begin
80
    if j = 0 then sTmp := sTmp + ' (';
81
    sTmp := sTmp + ListGetElement(coid.ASNIds, j);
82
    if j = jmax then
83
      sTmp := sTmp + ')'
84
    else
85
      sTmp := sTmp + ', ';
86
  end;
87
  FreeOidDef(@coid);
88
  _ShowASNIds := sTmp;
89
end;
90
 
91
function AsnAlreadyExisting(oid: POID; asnid: string): boolean;
92
var
93
  sTmp: string;
94
  i: integer;
95
begin
96
  for i := 0 to ListCount(oid^.AsnIds)-1 do
97
  begin
98
    sTmp := ListGetElement(oid^.AsnIds, i);
99
    if sTmp = asnid then
100
    begin
101
      AsnAlreadyExisting := true;
102
      exit;
103
    end;
104
  end;
105
  AsnAlreadyExisting := false;
106
end;
107
 
108
function AsnEditor(oid: POID): boolean;
109
var
110
  asnList: PStringList;
111
  i: integer;
112
  x, y, w, h: integer;
113
  res: integer;
114
  sInput: string;
115
begin
116
  AsnEditor := false;
117
 
118
  repeat
119
    InitList(asnList);
120
 
121
    for i := 0 to ListCount(oid^.ASNIds)-1 do
122
    begin
123
      ListAppend(asnList, ListGetElement(oid^.ASNIDs, i));
124
    end;
125
    ListAppend(asnList, '<NEW>');
126
    ListAppend(asnList, '<SAVE>');
127
    ListAppend(asnList, '<CANCEL>');
128
 
129
    DrawStatusBar(DEFAULT_STATUSBAR);
130
    x := SINGLE_LINE_BOX_PADDING;
131
    y := ScreenHeight div 2 - ASNEDIT_LINES div 2;
132
    w := ScreenWidth - (SINGLE_LINE_BOX_PADDING-1)*2;
133
    h := ASNEDIT_LINES;
134
    res := DrawSelectionList(x, y, w, h,
135
                             asnList, true,
136
                             'EDIT ASN.1 IDENTIFIERS',
137
                             2);
138
 
139
    (* Change double-border to thin-border *)
140
    DrawThinBorder(x-1, y-1, w+2, h+2);
141
    GoToXY(x+1, y-1);
142
    Write('EDIT ASN.1 IDENTIFIERS');
143
 
144
    if res = -1 then
145
    begin
146
      exit;
147
    end
148
    else if res = ListCount(oid^.ASNIDs) then
149
    begin
150
      (* "NEW" item was selected *)
151
      sInput := '';
152
      repeat
153
        if QueryVal(sInput,
154
                    SINGLE_LINE_BOX_PADDING_INNER,
155
                    ScreenHeight div 2,
156
                    ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
157
                    1,
158
                    'ADD SINGLE ASN.1 ID',
159
                    2) then
160
        begin
161
          if sInput = '' then continue;
162
          if not ASN1IDValid(sInput) then
163
          begin
164
            ShowMessage('Invalid ASN1.ID! (Require -, a..z, A..Z, 0..9, begin with a-z)', 'ERROR', true);
165
            _Pause;
166
          end
167
          else if AsnAlreadyExisting(oid, sInput) then
168
          begin
169
            ShowMessage('ASN.1 identifier is already existing on this arc', 'ERROR', true);
170
            _Pause;
171
          end
172
          else
173
          begin
174
            ListAppend(oid^.ASNIDs, sInput);
175
            break;
176
          end;
177
        end
178
        else break;
179
      until false;
180
    end
181
    else if res = ListCount(oid^.ASNIDs)+1 then
182
    begin
183
      (* "SAVE" item was selected *)
184
      AsnEditor := true;
185
      Exit;
186
    end
187
    else if res = ListCount(oid^.ASNIDs)+2 then
188
    begin
189
      (* "CANCEL" item was selected *)
190
      AsnEditor := false;
191
      Exit;
192
    end
193
    else
194
    begin
195
      DrawStatusBar('Note: Remove the text to delete the ASN.1 identifier');
196
      sInput := ListGetElement(oid^.ASNIDs, res);
197
      repeat
198
        if QueryVal(sInput,
199
                    SINGLE_LINE_BOX_PADDING_INNER,
200
                    ScreenHeight div 2,
201
                    ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
202
                    1,
203
                    'EDIT SINGLE ASN.1 ID',
204
                    2) then
205
        begin
206
          if sInput = '' then
207
          begin
208
            (* Empty input = Delete ASN.1 ID *)
209
            ListDeleteElement(oid^.ASNIDs, res);
210
            break;
211
          end
212
          else if not ASN1IDValid(sInput) then
213
          begin
214
            ShowMessage('Invalid ASN1.ID! (Require -, a..z, A..Z, 0..9, begin with a-z)', 'ERROR', true);
215
            _Pause;
216
          end
217
          else if AsnAlreadyExisting(oid, sInput) and
218
              not (ListGetElement(oid^.ASNIDs, res) = sInput) then
219
          begin
220
            ShowMessage('ASN.1 identifier is already existing on this arc', 'ERROR', true);
221
            _Pause;
222
          end
223
          else
224
          begin
225
            ListSetElement(oid^.ASNIDs, res, sInput);
226
            break;
227
          end;
228
        end
229
        else break;
230
      until false;
231
    end;
232
  until false;
233
end;
234
 
235
function DescEditor(oid: POID): boolean;
236
var
237
  sInput: string;
238
begin
239
  DescEditor := false;
240
 
241
  DrawStatusBar('Note: Press Ctrl+Return for a line-break.');
242
  sInput := oid^.description;
243
  if QueryVal(sInput,
244
              DESCEDIT_PADDING,
245
              ScreenHeight div 2 - DESCEDIT_LINES div 2,
246
              ScreenWidth - (DESCEDIT_PADDING-1)*2,
247
              DESCEDIT_LINES,
248
              'EDIT DESCRIPTION',
249
              2) then
250
  begin
251
    oid^.description := sInput;
734 daniel-mar 252
    DescEditor := true; (* request caller to save @oid *)
733 daniel-mar 253
  end;
254
end;
255
 
256
function NextPossibleFileID: string;
257
var
258
  DirInfo: SearchRec;
259
  list: PStringList;
260
  iId: LongInt;
261
  sId: string;
262
begin
263
  (* Put all found files into a list *)
264
  InitList(list);
265
  FindFirst('????????.OID', Archive, DirInfo);
266
  while DosError = 0 do
267
  begin
268
    sId := Copy(DirInfo.Name, 1, 8);
269
    ListAppend(list, sId);
270
    FindNext(DirInfo);
271
  end;
272
 
273
  (* Search for the first non existing item in the list *)
274
  sId := '';
275
  for iId := 0 to 99999999 do
276
  begin
277
    sId := ZeroPad(iId, 8);
278
    if not ListContains(list, sId) then break;
279
  end;
280
  NextPossibleFileId := sId;
281
  FreeList(list);
282
end;
283
 
284
function NumIdAlreadyExisting(parentOID: POID; sInput: string): boolean;
285
var
286
  searchDotNotation: string;
287
  sTmp: string;
288
  i: integer;
289
begin
290
  if parentOID^.DotNotation = '' then
291
    searchDotNotation := sInput
292
  else
293
    searchDotNotation := parentOID^.DotNotation + '.' + sInput;
294
  for i := 0 to ListCount(parentOID^.SubIds)-1 do
295
  begin
296
    sTmp := ListGetElement(parentOID^.SubIds, i);
297
    Delete(sTmp, 1, 8);
298
    if sTmp = searchDotNotation then
299
    begin
300
      NumIdAlreadyExisting := true;
301
      exit;
302
    end;
303
  end;
304
  NumIdAlreadyExisting := false;
305
end;
306
 
307
function NumIdEditor(oid: POID; parentOID: POID): boolean;
308
var
309
  sInput: string;
310
begin
311
  NumIdEditor := false;
312
  sInput := '';
313
 
314
  repeat
315
    if QueryVal(sInput,
316
                SINGLE_LINE_BOX_PADDING_INNER,
317
                ScreenHeight div 2,
318
                ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
319
                1,
320
                'ENTER NUMERIC ID',
321
                2) then
322
    begin
323
      if sInput = '' then continue;
324
      if not IsNumeric(sInput) then
325
      begin
326
        ShowMessage('Invalid numeric ID (must be a positive integer)', 'ERROR', true);
327
        _Pause;
328
      end
329
      else if (parentOID^.DotNotation='') and (StrToInt(sInput) > 2) then
330
      begin
331
        ShowMessage('Invalid numeric ID (root arc can only be 0, 1, or 2)', 'ERROR', true);
332
        _Pause;
333
      end
334
      else if ((parentOID^.DotNotation='0') or (parentOID^.DotNotation='1')) and (StrToInt(sInput) > 39) then
335
      begin
336
        ShowMessage('Invalid numeric ID (root 0 and 1 must have sub-arc of 0..39)', 'ERROR', true);
337
        _Pause;
338
      end
339
      else if NumIdAlreadyExisting(parentOID, sInput) then
340
      begin
341
        ShowMessage('This numeric ID is already used in this arc', 'ERROR', true);
342
        _Pause;
343
      end
344
      else
345
      begin
346
        if parentOID^.DotNotation = '' then
347
          oid^.DotNotation := sInput
348
        else
349
          oid^.DotNotation := parentOID^.DotNotation + '.' + sInput;
734 daniel-mar 350
        NumIdEditor := true; (* request caller to save @oid *)
733 daniel-mar 351
        Exit;
352
      end;
353
    end
354
    else
355
    begin
356
      Exit;
357
    end;
358
  until false;
359
end;
360
 
361
function NewOidEditor(oid: POID): boolean;
362
var
363
  newfilename: string;
364
  newoid: TOID;
365
begin
366
  NewOidEditor := false;
367
 
368
  InitOidDef(@newoid);
369
  newoid.FileId := NextPossibleFileID;
370
  newoid.Parent := oid^.FileId + oid^.DotNotation;
734 daniel-mar 371
  if NumIdEditor(@newoid, oid) and
372
     AsnEditor(@newoid) and
373
     DescEditor(@newoid) then
374
  begin
375
    newfilename := newoid.FileId + '.OID';
376
    _WriteOidFile(newfilename, @newoid);
733 daniel-mar 377
 
734 daniel-mar 378
    (* Add link to original file and enable the saving of it *)
379
    ListAppend(oid^.SubIds, newoid.FileId + newoid.DotNotation);
380
    NewOidEditor := true; (* request caller to save @oid *)
381
  end;
382
  FreeOidDef(@newoid);
733 daniel-mar 383
end;
384
 
385
procedure DeleteChildrenRecursive(oid: POID);
386
var
387
  i: integer;
388
  childOID: TOID;
389
  filenameChild: string;
390
begin
391
  for i := 0 to ListCount(oid^.SubIds)-1 do
392
  begin
393
    filenameChild := Copy(ListGetElement(oid^.SubIds, i), 1, 8) + '.OID';
394
    InitOidDef(@childOID);
395
    _ReadOidFile(filenameChild, @childOID);
396
    DeleteChildrenRecursive(@childOID);
397
    FreeOidDef(@childOID);
398
    DeleteFile(filenameChild);
399
  end;
400
  ListClear(oid^.SubIds);
401
end;
402
 
403
procedure DeleteOidRecursive(selfOID: POID);
404
var
405
  i: integer;
406
  parentOID: TOID;
407
  filenameSelf, filenameParent: string;
408
  fileIdToDelete: string;
409
begin
410
  (* Remove all children and their files recursively *)
411
  DeleteChildrenRecursive(selfOID);
412
 
413
  (* Remove forward reference in parent OID *)
414
  filenameParent := Copy(selfOID^.Parent, 1, 8)+'.OID';
415
  InitOidDef(@parentOID);
416
  _ReadOidFile(filenameParent, @parentOID);
417
  for i := 0 to ListCount(parentOID.SubIds)-1 do
418
  begin
419
    if Copy(ListGetElement(parentOID.SubIds, i), 1, 8) = selfOID^.FileId then
420
    begin
421
      ListDeleteElement(parentOID.SubIds, i);
422
      _WriteOidFile(filenameParent, @parentOID);
423
      break;
424
    end;
425
  end;
426
  FreeOidDef(@parentOID);
427
 
428
  (* Delete own file *)
429
  fileIdToDelete := selfOID^.FileId;
430
  filenameSelf := fileIdToDelete+'.OID';
431
  DeleteFile(filenameSelf);
432
end;
433
 
434
procedure DisplayOIDFile(filename: string);
435
const
436
  ID_EXIT     = '?EXIT';
437
  ID_ASNEDIT  = '?ASN1';
438
  ID_DESCEDIT = '?DESC';
439
  ID_ADDCHILD = '?ADDC';
440
  ID_DELETE   = '?DELE';
441
  NAVBAR_SIZE = 5;
442
var
443
  isRoot: boolean;
444
  f: Text;
445
  line, cmd: string;
446
  oid: TOID;
447
  i, menuX, menuY: integer;
448
  linesLeft, linesRequired: integer;
449
  sTmp, subfile: string;
450
  sAsn: string;
451
  subsel, subfiles: PStringList;
452
  subselres: integer;
453
  sTmp1: string;
454
begin
455
  repeat
456
    InitOidDef(@oid);
457
    _ReadOidFile(filename, @oid);
458
 
459
    (* Print OID information *)
460
 
461
    ClrScr;
462
 
463
    if oid.DotNotation = '' then
464
      DrawTitleBar('OID ROOT')
465
    else
466
      DrawTitleBar('OID ' + oid.DotNotation);
467
    GotoXY(ScreenWidth-Length(filename)+1,1);
468
    TextBackground(White);
469
    TextColor(Black);
470
    WriteLn(filename);
471
    TextBackground(Black);
472
    TextColor(White);
473
    DrawStatusBar(DEFAULT_STATUSBAR);
474
    GotoXY(1,2);
475
 
476
    if oid.DotNotation <> '' then
477
    begin
478
      WriteLn('Dot-Notation:');
479
      WriteLn(oid.DotNotation);
480
      WriteLn('');
481
    end;
482
 
483
    if Trim(oid.Description) <> '' then
484
    begin
485
      WriteLn('Description:');
486
      WriteLn(oid.Description);
487
      WriteLn('');
488
    end;
489
 
490
    menuX := WhereX + 1;
491
    menuY := ScreenHeight - NAVBAR_SIZE - 1;
492
 
493
    if ListCount(oid.ASNIDs) > 0 then
494
    begin
495
      linesLeft := menuY - WhereY - 1;
496
      linesRequired := 1 + ListCount(oid.ASNIds);
497
 
498
      if LinesLeft < LinesRequired then
499
      begin
500
        (* Compact display of ASN.1 identifiers *)
501
        Write('ASN.1-Identifiers: ');
502
        for i := 0 to ListCount(oid.ASNIds)-1 do
503
        begin
504
          if i > 0 then Write(', ');
505
          Write(ListGetElement(oid.ASNIds, i));
506
        end;
507
        WriteLn('');
508
      end
509
      else
510
      begin
511
        (* Long display of ASN.1 identifiers *)
512
        WriteLn('ASN.1-Identifiers:');
513
        for i := 0 to ListCount(oid.ASNIds)-1 do
514
        begin
515
          WriteLn('- '+ListGetElement(oid.ASNIds, i));
516
        end;
517
        WriteLn('');
518
      end;
519
    end;
520
 
521
    (* Now prepare the menu entries *)
522
 
523
    InitList(subsel);
524
    InitList(subfiles);
525
 
526
    sTmp := oid.Parent;
527
    if sTmp = '' then
528
    begin
529
      isRoot := true;
530
    end
531
    else
532
    begin
533
      Delete(sTmp, 1, 8);
534
      isRoot := sTmp = oid.DotNotation;
535
    end;
536
 
537
    if (oid.Parent <> '') and not isRoot then
538
    begin
539
      sTmp := oid.Parent;
540
      subfile := Copy(sTmp, 1, 8)+'.OID';
541
      Delete(sTmp, 1, 8);
542
      sTmp := sTmp + _ShowASNIds(subfile);
543
      ListAppend(subsel, 'Go to parent ' + sTmp);
544
      ListAppend(subfiles, subfile);
545
    end;
546
 
547
    if isRoot then
548
    begin
549
      ListAppend(subsel, 'Back to main menu');
550
      ListAppend(subfiles, ID_EXIT);
551
    end;
552
 
553
    for i := 0 to ListCount(oid.SubIds)-1 do
554
    begin
555
      sTmp := ListGetElement(oid.SubIds, i);
556
      subfile := Copy(sTmp, 1, 8)+'.OID';
557
      Delete(sTmp, 1, 8);
558
      sTmp := sTmp + _ShowASNIds(subfile);
559
      ListAppend(subsel, 'Go to child  ' + sTmp);
560
      ListAppend(subfiles, subfile);
561
    end;
562
 
563
    if oid.DotNotation <> '' then
564
    begin
565
      ListAppend(subsel, 'Edit ASN.1 identifiers');
566
      ListAppend(subfiles, ID_ASNEDIT);
567
    end;
568
 
569
    ListAppend(subsel, 'Edit description');
570
    ListAppend(subfiles, ID_DESCEDIT);
571
 
572
    ListAppend(subsel, 'Add child');
573
    ListAppend(subfiles, ID_ADDCHILD);
574
 
575
    if not isRoot then
576
    begin
577
      ListAppend(subsel, 'Delete OID');
578
      ListAppend(subfiles, ID_DELETE);
579
    end;
580
 
734 daniel-mar 581
    (* Show menu *)
582
 
733 daniel-mar 583
    subselres := DrawSelectionList(menuX, menuY,
584
                                   ScreenWidth-2,
585
                                   NAVBAR_SIZE,
586
                                   subsel,
587
                                   true,
588
                                   'SELECT ACTION',
589
                                   1);
734 daniel-mar 590
 
591
    (* Process user selection *)
592
 
733 daniel-mar 593
    if subselres = -1 then
594
    begin
595
      exit;
596
    end
597
    else
598
    begin
599
      sTmp1 := ListGetElement(subfiles, subselres);
600
      if sTmp1 = ID_ASNEDIT then
601
      begin
602
        if AsnEditor(@oid) then
603
          _WriteOidFile(filename, @oid);
604
      end
605
      else if sTmp1 = ID_DESCEDIT then
606
      begin
607
        if DescEditor(@oid) then
608
          _WriteOidFile(filename, @oid);
609
      end
610
      else if sTmp1 = ID_ADDCHILD then
611
      begin
612
        if NewOidEditor(@oid) then
613
        begin
614
          _WriteOidFile(filename, @oid);
615
        end;
616
      end
617
      else if sTmp1 = ID_DELETE then
618
      begin
619
        ShowMessage('Are you sure you want to delete this OID?', 'DELETE OID', true);
620
        DrawStatusBar('Y = Yes; any other key = No');
621
        if UpCase(ReadKey) = 'Y' then
622
        begin
623
          filename := Copy(oid.Parent,1,8)+'.OID';
624
          DeleteOidRecursive(@oid);
625
        end;
626
      end
627
      else if sTmp1 = ID_EXIT then
628
      begin
629
        exit;
630
      end
631
      else
632
      begin
633
        filename := sTmp1;
634
      end;
635
    end;
636
    FreeList(subsel);
637
    FreeList(subfiles);
638
 
639
    FreeOidDef(@oid);
640
  until false;
641
end;
642
 
643
procedure CreateInitOIDFile(filename: string);
644
var
645
  oid: TOID;
646
begin
647
  InitOidDef(@oid);
648
  oid.Description := 'This is the root of the OID tree.' +#13#10 +
649
                     #13#10 +
650
                     'Valid subsequent arcs are per definition:' + #13#10 +
651
                     '- 0 (itu-t)' + #13#10 +
652
                     '- 1 (iso)' + #13#10 +
653
                     '- 2 (joint-iso-itu-t)';
654
  oid.FileId      := '00000000';
655
  oid.DotNotation := '';
656
  oid.Parent      := '00000000';
657
  _WriteOidFile(filename, @oid);
658
  FreeOidDef(@oid);
659
end;
660
 
661
procedure OP_ManageOIDs;
662
begin
663
  ClrScr;
664
  DrawTitleBar('Manage Object Identifiers');
665
  DrawStatusBar('');
666
 
667
  if not FileExists('00000000.OID') then
668
  begin
669
    CreateInitOIDFile('00000000.OID');
670
  end;
671
  DisplayOIDFile('00000000.OID');
672
end;
673
 
674
procedure OP_ManageRAs;
675
begin
676
  ClrScr;
677
  DrawTitleBar('Manage Registration Authorities');
678
  DrawStatusBar('');
679
 
680
  (* TODO: Implement "Manage RAs" feature *)
681
  ShowMessage('This feature has not yet been implemented!', 'NOTICE', true);
682
  _Pause;
683
end;
684
 
685
procedure OP_ReturnToMSDOS;
686
begin
687
  ClrScr;
688
end;
689
 
690
procedure OP_MainMenu;
691
const
692
  MenuWidth = 15;
693
  MenuHeight = 3;
694
var
695
  menu: PStringList;
696
  menuRes, menuLeft, menuTop: integer;
697
begin
698
  repeat
699
    ClrScr;
700
 
701
    DrawTitleBar('Welcome to OIDplus for DOS');
702
    DrawStatusBar(DEFAULT_STATUSBAR);
703
    GoToXY(ScreenWidth-Length(VERSIONINFO), ScreenHeight-1);
704
    Write(VERSIONINFO);
705
 
706
    InitList(menu);
707
    ListAppend(menu, 'Manage OIDs');
708
    ListAppend(menu, 'Manage RAs');
709
    ListAppend(menu, 'Return to DOS');
710
    menuLeft := round(ScreenWidth/2 -MenuWidth/2);
711
    menuTop  := round(ScreenHeight/2-MenuHeight/2);
712
    menuRes  := DrawSelectionList(menuLeft, menuTop, MenuWidth, MenuHeight, menu, true, 'MAIN MENU', 2);
713
    FreeList(menu);
714
 
715
    if menuRes = 0 then
716
    begin
717
      OP_ManageOIDs;
718
    end;
719
 
720
    if menuRes = 1 then
721
    begin
722
      OP_ManageRAs;
723
    end;
724
  until (menuRes = 2) or (menuRes = -1);
725
 
726
  OP_ReturnToMSDOS;
727
end;
728
 
729
begin
730
  OP_MainMenu;
731
end.