Subversion Repositories oidplus

Rev

Rev 734 | Go to most recent revision | Details | 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                   *)
6
(* Revision: 2022-02-13                         *)
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
16
  VERSIONINFO = 'Revision: 2022-02-13';
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;
252
    DescEditor := true; (* enable write file *)
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;
350
        NumIdEditor := true;
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;
371
  if not NumIdEditor(@newoid, oid) then exit;
372
  if not AsnEditor(@newoid) then exit;
373
  if not DescEditor(@newoid) then exit;
374
 
375
  newfilename := newoid.FileId + '.OID';
376
  _WriteOidFile(newfilename, @newoid);
377
 
378
  (* Add link to original file and enable the saving of it *)
379
  ListAppend(oid^.SubIds, newoid.FileId + newoid.DotNotation);
380
  NewOidEditor := true;
381
end;
382
 
383
procedure DeleteChildrenRecursive(oid: POID);
384
var
385
  i: integer;
386
  childOID: TOID;
387
  filenameChild: string;
388
begin
389
  for i := 0 to ListCount(oid^.SubIds)-1 do
390
  begin
391
    filenameChild := Copy(ListGetElement(oid^.SubIds, i), 1, 8) + '.OID';
392
    InitOidDef(@childOID);
393
    _ReadOidFile(filenameChild, @childOID);
394
    DeleteChildrenRecursive(@childOID);
395
    FreeOidDef(@childOID);
396
    DeleteFile(filenameChild);
397
  end;
398
  ListClear(oid^.SubIds);
399
end;
400
 
401
procedure DeleteOidRecursive(selfOID: POID);
402
var
403
  i: integer;
404
  parentOID: TOID;
405
  filenameSelf, filenameParent: string;
406
  fileIdToDelete: string;
407
begin
408
  (* Remove all children and their files recursively *)
409
  DeleteChildrenRecursive(selfOID);
410
 
411
  (* Remove forward reference in parent OID *)
412
  filenameParent := Copy(selfOID^.Parent, 1, 8)+'.OID';
413
  InitOidDef(@parentOID);
414
  _ReadOidFile(filenameParent, @parentOID);
415
  for i := 0 to ListCount(parentOID.SubIds)-1 do
416
  begin
417
    if Copy(ListGetElement(parentOID.SubIds, i), 1, 8) = selfOID^.FileId then
418
    begin
419
      ListDeleteElement(parentOID.SubIds, i);
420
      _WriteOidFile(filenameParent, @parentOID);
421
      break;
422
    end;
423
  end;
424
  FreeOidDef(@parentOID);
425
 
426
  (* Delete own file *)
427
  fileIdToDelete := selfOID^.FileId;
428
  filenameSelf := fileIdToDelete+'.OID';
429
  DeleteFile(filenameSelf);
430
end;
431
 
432
procedure DisplayOIDFile(filename: string);
433
const
434
  ID_EXIT     = '?EXIT';
435
  ID_ASNEDIT  = '?ASN1';
436
  ID_DESCEDIT = '?DESC';
437
  ID_ADDCHILD = '?ADDC';
438
  ID_DELETE   = '?DELE';
439
  NAVBAR_SIZE = 5;
440
var
441
  isRoot: boolean;
442
  f: Text;
443
  line, cmd: string;
444
  oid: TOID;
445
  i, menuX, menuY: integer;
446
  linesLeft, linesRequired: integer;
447
  sTmp, subfile: string;
448
  sAsn: string;
449
  subsel, subfiles: PStringList;
450
  subselres: integer;
451
  sTmp1: string;
452
begin
453
  repeat
454
    InitOidDef(@oid);
455
    _ReadOidFile(filename, @oid);
456
 
457
    (* Print OID information *)
458
 
459
    ClrScr;
460
 
461
    if oid.DotNotation = '' then
462
      DrawTitleBar('OID ROOT')
463
    else
464
      DrawTitleBar('OID ' + oid.DotNotation);
465
    GotoXY(ScreenWidth-Length(filename)+1,1);
466
    TextBackground(White);
467
    TextColor(Black);
468
    WriteLn(filename);
469
    TextBackground(Black);
470
    TextColor(White);
471
    DrawStatusBar(DEFAULT_STATUSBAR);
472
    GotoXY(1,2);
473
 
474
    if oid.DotNotation <> '' then
475
    begin
476
      WriteLn('Dot-Notation:');
477
      WriteLn(oid.DotNotation);
478
      WriteLn('');
479
    end;
480
 
481
    if Trim(oid.Description) <> '' then
482
    begin
483
      WriteLn('Description:');
484
      WriteLn(oid.Description);
485
      WriteLn('');
486
    end;
487
 
488
    menuX := WhereX + 1;
489
    menuY := ScreenHeight - NAVBAR_SIZE - 1;
490
 
491
    if ListCount(oid.ASNIDs) > 0 then
492
    begin
493
      linesLeft := menuY - WhereY - 1;
494
      linesRequired := 1 + ListCount(oid.ASNIds);
495
 
496
      if LinesLeft < LinesRequired then
497
      begin
498
        (* Compact display of ASN.1 identifiers *)
499
        Write('ASN.1-Identifiers: ');
500
        for i := 0 to ListCount(oid.ASNIds)-1 do
501
        begin
502
          if i > 0 then Write(', ');
503
          Write(ListGetElement(oid.ASNIds, i));
504
        end;
505
        WriteLn('');
506
      end
507
      else
508
      begin
509
        (* Long display of ASN.1 identifiers *)
510
        WriteLn('ASN.1-Identifiers:');
511
        for i := 0 to ListCount(oid.ASNIds)-1 do
512
        begin
513
          WriteLn('- '+ListGetElement(oid.ASNIds, i));
514
        end;
515
        WriteLn('');
516
      end;
517
    end;
518
 
519
    (* Now prepare the menu entries *)
520
 
521
    InitList(subsel);
522
    InitList(subfiles);
523
 
524
    sTmp := oid.Parent;
525
    if sTmp = '' then
526
    begin
527
      isRoot := true;
528
    end
529
    else
530
    begin
531
      Delete(sTmp, 1, 8);
532
      isRoot := sTmp = oid.DotNotation;
533
    end;
534
 
535
    if (oid.Parent <> '') and not isRoot then
536
    begin
537
      sTmp := oid.Parent;
538
      subfile := Copy(sTmp, 1, 8)+'.OID';
539
      Delete(sTmp, 1, 8);
540
      sTmp := sTmp + _ShowASNIds(subfile);
541
      ListAppend(subsel, 'Go to parent ' + sTmp);
542
      ListAppend(subfiles, subfile);
543
    end;
544
 
545
    if isRoot then
546
    begin
547
      ListAppend(subsel, 'Back to main menu');
548
      ListAppend(subfiles, ID_EXIT);
549
    end;
550
 
551
    for i := 0 to ListCount(oid.SubIds)-1 do
552
    begin
553
      sTmp := ListGetElement(oid.SubIds, i);
554
      subfile := Copy(sTmp, 1, 8)+'.OID';
555
      Delete(sTmp, 1, 8);
556
      sTmp := sTmp + _ShowASNIds(subfile);
557
      ListAppend(subsel, 'Go to child  ' + sTmp);
558
      ListAppend(subfiles, subfile);
559
    end;
560
 
561
    if oid.DotNotation <> '' then
562
    begin
563
      ListAppend(subsel, 'Edit ASN.1 identifiers');
564
      ListAppend(subfiles, ID_ASNEDIT);
565
    end;
566
 
567
    ListAppend(subsel, 'Edit description');
568
    ListAppend(subfiles, ID_DESCEDIT);
569
 
570
    ListAppend(subsel, 'Add child');
571
    ListAppend(subfiles, ID_ADDCHILD);
572
 
573
    if not isRoot then
574
    begin
575
      ListAppend(subsel, 'Delete OID');
576
      ListAppend(subfiles, ID_DELETE);
577
    end;
578
 
579
    subselres := DrawSelectionList(menuX, menuY,
580
                                   ScreenWidth-2,
581
                                   NAVBAR_SIZE,
582
                                   subsel,
583
                                   true,
584
                                   'SELECT ACTION',
585
                                   1);
586
    if subselres = -1 then
587
    begin
588
      exit;
589
    end
590
    else
591
    begin
592
      sTmp1 := ListGetElement(subfiles, subselres);
593
      if sTmp1 = ID_ASNEDIT then
594
      begin
595
        if AsnEditor(@oid) then
596
          _WriteOidFile(filename, @oid);
597
      end
598
      else if sTmp1 = ID_DESCEDIT then
599
      begin
600
        if DescEditor(@oid) then
601
          _WriteOidFile(filename, @oid);
602
      end
603
      else if sTmp1 = ID_ADDCHILD then
604
      begin
605
        if NewOidEditor(@oid) then
606
        begin
607
          _WriteOidFile(filename, @oid);
608
        end;
609
      end
610
      else if sTmp1 = ID_DELETE then
611
      begin
612
        ShowMessage('Are you sure you want to delete this OID?', 'DELETE OID', true);
613
        DrawStatusBar('Y = Yes; any other key = No');
614
        if UpCase(ReadKey) = 'Y' then
615
        begin
616
          filename := Copy(oid.Parent,1,8)+'.OID';
617
          DeleteOidRecursive(@oid);
618
        end;
619
      end
620
      else if sTmp1 = ID_EXIT then
621
      begin
622
        exit;
623
      end
624
      else
625
      begin
626
        filename := sTmp1;
627
      end;
628
    end;
629
    FreeList(subsel);
630
    FreeList(subfiles);
631
 
632
    FreeOidDef(@oid);
633
  until false;
634
end;
635
 
636
procedure CreateInitOIDFile(filename: string);
637
var
638
  oid: TOID;
639
begin
640
  InitOidDef(@oid);
641
  oid.Description := 'This is the root of the OID tree.' +#13#10 +
642
                     #13#10 +
643
                     'Valid subsequent arcs are per definition:' + #13#10 +
644
                     '- 0 (itu-t)' + #13#10 +
645
                     '- 1 (iso)' + #13#10 +
646
                     '- 2 (joint-iso-itu-t)';
647
  oid.FileId      := '00000000';
648
  oid.DotNotation := '';
649
  oid.Parent      := '00000000';
650
  _WriteOidFile(filename, @oid);
651
  FreeOidDef(@oid);
652
end;
653
 
654
procedure OP_ManageOIDs;
655
begin
656
  ClrScr;
657
  DrawTitleBar('Manage Object Identifiers');
658
  DrawStatusBar('');
659
 
660
  if not FileExists('00000000.OID') then
661
  begin
662
    CreateInitOIDFile('00000000.OID');
663
  end;
664
  DisplayOIDFile('00000000.OID');
665
end;
666
 
667
procedure OP_ManageRAs;
668
begin
669
  ClrScr;
670
  DrawTitleBar('Manage Registration Authorities');
671
  DrawStatusBar('');
672
 
673
  (* TODO: Implement "Manage RAs" feature *)
674
  ShowMessage('This feature has not yet been implemented!', 'NOTICE', true);
675
  _Pause;
676
end;
677
 
678
procedure OP_ReturnToMSDOS;
679
begin
680
  ClrScr;
681
end;
682
 
683
procedure OP_MainMenu;
684
const
685
  MenuWidth = 15;
686
  MenuHeight = 3;
687
var
688
  menu: PStringList;
689
  menuRes, menuLeft, menuTop: integer;
690
begin
691
  repeat
692
    ClrScr;
693
 
694
    DrawTitleBar('Welcome to OIDplus for DOS');
695
    DrawStatusBar(DEFAULT_STATUSBAR);
696
    GoToXY(ScreenWidth-Length(VERSIONINFO), ScreenHeight-1);
697
    Write(VERSIONINFO);
698
 
699
    InitList(menu);
700
    ListAppend(menu, 'Manage OIDs');
701
    ListAppend(menu, 'Manage RAs');
702
    ListAppend(menu, 'Return to DOS');
703
    menuLeft := round(ScreenWidth/2 -MenuWidth/2);
704
    menuTop  := round(ScreenHeight/2-MenuHeight/2);
705
    menuRes  := DrawSelectionList(menuLeft, menuTop, MenuWidth, MenuHeight, menu, true, 'MAIN MENU', 2);
706
    FreeList(menu);
707
 
708
    if menuRes = 0 then
709
    begin
710
      OP_ManageOIDs;
711
    end;
712
 
713
    if menuRes = 1 then
714
    begin
715
      OP_ManageRAs;
716
    end;
717
  until (menuRes = 2) or (menuRes = -1);
718
 
719
  OP_ReturnToMSDOS;
720
end;
721
 
722
begin
723
  OP_MainMenu;
724
end.