Subversion Repositories SvarDOS

Rev

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

Rev Author Line No. Line
28 mv_fox 1
/*
190 mateuszvis 2
 * SVARDOS INSTALL PROGRAM
206 mateuszvis 3
 *
190 mateuszvis 4
 * PUBLISHED UNDER THE TERMS OF THE MIT LICENSE
42 mv_fox 5
 *
1624 mateusz.vi 6
 * COPYRIGHT (C) 2016-2024 MATEUSZ VISTE, ALL RIGHTS RESERVED.
94 mv_fox 7
 *
190 mateuszvis 8
 * Permission is hereby granted, free of charge, to any person obtaining a
9
 * copy of this software and associated documentation files (the "Software"),
10
 * to deal in the Software without restriction, including without limitation
11
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12
 * and/or sell copies of the Software, and to permit persons to whom the
13
 * Software is furnished to do so, subject to the following conditions:
94 mv_fox 14
 *
190 mateuszvis 15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
94 mv_fox 17
 *
190 mateuszvis 18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24
 * DEALINGS IN THE SOFTWARE.
94 mv_fox 25
 *
868 mateusz.vi 26
 * http://svardos.org
28 mv_fox 27
 */
28
 
29
#include <dos.h>
30 mv_fox 30
#include <direct.h>  /* mkdir() */
28 mv_fox 31
#include <stdio.h>   /* printf() and friends */
32
#include <stdlib.h>  /* system() */
33
#include <string.h>  /* memcpy() */
34
#include <unistd.h>
42 mv_fox 35
 
1662 mateusz.vi 36
#include "mdr\cout.h"
1661 mateusz.vi 37
#include "mdr\dos.h"
624 mateuszvis 38
#include "svarlang.lib\svarlang.h"
42 mv_fox 39
 
67 mv_fox 40
/* keyboard layouts and locales */
41
#include "keylay.h"
42
#include "keyoff.h"
42 mv_fox 43
 
908 mateusz.vi 44
 
1664 mateusz.vi 45
/* color scheme (preset for color) */
46
static unsigned char COLOR_TITLEBAR  = 0x70;
1671 mateusz.vi 47
static unsigned char COLOR_TITLEVER  = 0x78;
1664 mateusz.vi 48
static unsigned char COLOR_BODY      = 0x17;
1673 mateusz.vi 49
static unsigned char COLOR_BODYWARN  = 0x1F;
1664 mateusz.vi 50
static unsigned char COLOR_SELECT    = 0x70;
51
static unsigned char COLOR_SELECTCUR = 0x1F;
28 mv_fox 52
 
1671 mateusz.vi 53
/* build release string, populated at startup by reading floppy's label */
54
static char BUILDSTRING[13];
28 mv_fox 55
 
190 mateuszvis 56
/* how much disk space does SvarDOS require (in MiB) */
1928 mateusz.vi 57
#define SVARDOS_DISK_REQ 4
28 mv_fox 58
 
73 mv_fox 59
/* menu screens can output only one of these: */
60
#define MENUNEXT 0
61
#define MENUPREV -1
62
#define MENUQUIT -2
63
 
67 mv_fox 64
/* a convenience 'function' used for debugging */
65
#define DBG(x) { video_putstringfix(24, 0, 0x4F00u, x, 80); }
47 mv_fox 66
 
67 mv_fox 67
struct slocales {
68
  char lang[4];
624 mateuszvis 69
  const char *keybcode;
1908 mateusz.vi 70
  unsigned short codepage;
71
  unsigned char egafile;
72
  unsigned char keybfile;
73
  short keyboff;
74
  short keyblen;
75
  unsigned short keybid;
76
  unsigned short countryid; /* 1=USA, 33=FR, 48=PL, etc */
67 mv_fox 77
};
78
 
79
 
1918 mateusz.vi 80
/* install a dummy int24h handler that always fails. this is to avoid the
81
 * annoying "abort, retry, fail... DOS messages. */
82
static void install_int24(void) {
83
  static unsigned char handler[] = { /* contains machine code instructions */
84
    0xB0, 0x03,  /* mov al, 3   ; tell DOS the action has to FAIL   */
85
    0xCF};       /* ret         ; return from the interrupt handler */
86
  /* install the handler */
87
  _asm {
88
    push dx
89
    mov ax, 0x2524          /* set INT vector 0x24 (to DS:DX)   */
90
    mov dx, offset handler  /* DS:DX points at my dummy handler */
91
    int 0x21
92
    pop dx
93
  }
94
}
95
 
96
 
97
static void exec(const char *s) {
98
  system(s);
99
  install_int24(); /* reinstall my int24 handler, apparently system() reverts
100
                      the original (DOS) one */
101
}
102
 
103
 
1908 mateusz.vi 104
/* put a string on screen and fill it until w chars with white space */
1662 mateusz.vi 105
static void video_putstringfix(unsigned char y, unsigned char x, unsigned char attr, const char *s, unsigned char w) {
106
  unsigned char i;
107
 
108
  /* print the string up to w characters */
109
  i = mdr_cout_str(y, x, s, attr, w);
110
 
111
  /* fill in left space (if any) with blanks */
112
  mdr_cout_char_rep(y, x + i, ' ', attr, w - i);
113
}
114
 
115
 
28 mv_fox 116
/* reboot the computer */
117
static void reboot(void) {
118
  void ((far *bootroutine)()) = (void (far *)()) 0xFFFF0000L;
119
  int far *rstaddr = (int far *)0x00400072L; /* BIOS boot flag is at 0040:0072 */
120
  *rstaddr = 0x1234; /* 0x1234 = warm boot, 0 = cold boot */
121
  (*bootroutine)(); /* jump to the BIOS reboot routine at FFFF:0000 */
122
}
123
 
42 mv_fox 124
 
1669 mateusz.vi 125
/* returns 1 if file exists, zero otherwise */
126
static int fileexists(const char *fname) {
127
  FILE *fd;
128
  fd = fopen(fname, "rb");
129
  if (fd == NULL) return(0);
130
  fclose(fd);
131
  return(1);
132
}
133
 
134
 
56 mv_fox 135
/* outputs a string to screen with taking care of word wrapping. returns amount of lines. */
1662 mateusz.vi 136
static unsigned char putstringwrap(unsigned char y, unsigned char x, unsigned char attr, const char *s) {
137
  unsigned char linew, lincount;
138
  linew = 80 - (x << 1);
56 mv_fox 139
 
140
  for (lincount = 1; y+lincount < 25; lincount++) {
141
    int i, len = linew;
142
    for (i = 0; i <= linew; i++) {
143
      if (s[i] == ' ') len = i;
144
      if (s[i] == '\n') {
145
        len = i;
146
        break;
147
      }
148
      if (s[i] == 0) {
149
        len = i;
150
        break;
151
      }
152
    }
1662 mateusz.vi 153
    mdr_cout_str(y++, x, s, attr, len);
56 mv_fox 154
    s += len;
155
    if (*s == 0) break;
156
    s += 1; /* skip the whitespace char */
157
  }
158
  return(lincount);
159
}
160
 
161
 
162
/* an NLS wrapper around video_putstring(), also performs line wrapping when
163
 * needed. returns the amount of lines that were output */
1662 mateusz.vi 164
static unsigned char putstringnls(unsigned char y, unsigned char x, unsigned char attr, unsigned char nlsmaj, unsigned char nlsmin) {
624 mateuszvis 165
  const char *s = svarlang_str(nlsmaj, nlsmin);
166
  if (s == NULL) s = "";
56 mv_fox 167
  return(putstringwrap(y, x, attr, s));
42 mv_fox 168
}
169
 
170
 
280 mateuszvis 171
/* copy file f1 to f2 using buff as a buffer of buffsz bytes. f2 will be overwritten if it
172
 * exists already! returns 0 on success. */
173
static int fcopy(const char *f2, const char *f1, void *buff, size_t buffsz) {
174
  FILE *fd1, *fd2;
175
  size_t sz;
176
  int res = -1; /* assume failure */
177
 
178
  /* open files */
179
  fd1 = fopen(f1, "rb");
180
  fd2 = fopen(f2, "wb");
181
  if ((fd1 == NULL) || (fd2 == NULL)) goto QUIT;
182
 
183
  /* copy data */
184
  for (;;) {
185
    sz = fread(buff, 1, buffsz, fd1);
186
    if (sz == 0) {
187
      if (feof(fd1) != 0) break;
188
      goto QUIT;
189
    }
190
    if (fwrite(buff, 1, sz, fd2) != sz) goto QUIT;
191
  }
192
 
193
  res = 0; /* success */
194
 
195
  QUIT:
196
  if (fd1 != NULL) fclose(fd1);
197
  if (fd2 != NULL) fclose(fd2);
198
  return(res);
199
}
200
 
201
 
1666 mateusz.vi 202
/* display a menu with items and return user's choice.
203
 * ypos: starting line where the menu is drawn
1948 mateusz.vi 204
 * height: max number of items to display inside the menu
1666 mateusz.vi 205
 * list: NULL-terminated list of items
206
 * maxlistlen: limit list to this many items tops */
1665 mateusz.vi 207
static int menuselect(unsigned char ypos, unsigned char height, const char **list, int maxlistlen) {
208
  int i, offset = 0, res = 0, count;
209
  unsigned char y, xpos, width = 0;
1662 mateusz.vi 210
 
1666 mateusz.vi 211
  /* count how many positions there are, and check their width */
1665 mateusz.vi 212
  for (count = 0; (list[count] != NULL) && (count != maxlistlen); count++) {
28 mv_fox 213
    int len = strlen(list[count]);
214
    if (len > width) width = len;
215
  }
1666 mateusz.vi 216
  width++; /* it's nice to have a small margin to the right of the widest item */
28 mv_fox 217
 
1948 mateusz.vi 218
  /* adjust height if there is less items than max height */
219
  if (count < height) height = count;
220
 
28 mv_fox 221
  /* if xpos negative, means 'center out' */
1665 mateusz.vi 222
  xpos = 39 - (width >> 1);
28 mv_fox 223
 
1666 mateusz.vi 224
  mdr_cout_char_rep(ypos, xpos, 0xC4, COLOR_SELECT, width + 2);  /* top line */
1664 mateusz.vi 225
  mdr_cout_char(ypos, xpos+width+2, 0xBF, COLOR_SELECT);         /*       \ */
226
  mdr_cout_char(ypos, xpos-1, 0xDA, COLOR_SELECT);               /*  /      */
1666 mateusz.vi 227
  ypos++; /* from now on ypos relates to the position of the content */
228
  mdr_cout_char(ypos+height, xpos-1, 0xC0, COLOR_SELECT);      /*  \      */
229
  mdr_cout_char(ypos+height, xpos+width+2, 0xD9, COLOR_SELECT);/*      /  */
230
  mdr_cout_char_rep(ypos+height, xpos, 0xC4, COLOR_SELECT, width + 2);
28 mv_fox 231
 
232
  for (;;) {
233
    int key;
1666 mateusz.vi 234
 
235
    /* draw side borders of the menu + the cursor */
236
    if (count <= height) { /* no need for a cursor, all fits on one page */
237
      i = 255;
238
    } else {
239
      i = offset * (height - 1) / (count - height);
240
    }
241
 
242
    for (y = ypos; y < (ypos + height); y++) {
243
      mdr_cout_char(y, xpos-1, 0xB3, COLOR_SELECT); /* left side */
244
      if (y - ypos == i) {
1667 mateusz.vi 245
        mdr_cout_char(y, xpos+width+2, '=', COLOR_SELECT); /* cursor */
1666 mateusz.vi 246
      } else {
247
        mdr_cout_char(y, xpos+width+2, 0xB3, COLOR_SELECT); /* right side */
248
      }
249
    }
250
 
28 mv_fox 251
    /* list of selectable items */
1666 mateusz.vi 252
    for (i = 0; i < height; i++) {
28 mv_fox 253
      if (i + offset == res) {
1666 mateusz.vi 254
        mdr_cout_char(ypos + i, xpos, 16, COLOR_SELECTCUR);
255
        mdr_cout_char(ypos + i, xpos+width+1, 17, COLOR_SELECTCUR);
256
        mdr_cout_locate(ypos + i, xpos);
257
        video_putstringfix(ypos + i, xpos+1, COLOR_SELECTCUR, list[i + offset], width);
28 mv_fox 258
      } else if (i + offset < count) {
1666 mateusz.vi 259
        mdr_cout_char(ypos + i, xpos, ' ', COLOR_SELECT);
260
        mdr_cout_char(ypos + i, xpos+width+1, ' ', COLOR_SELECT);
261
        video_putstringfix(ypos + i, xpos+1, COLOR_SELECT, list[i + offset], width);
28 mv_fox 262
      } else {
1666 mateusz.vi 263
        mdr_cout_char_rep(ypos + i, xpos, ' ', COLOR_SELECT, width+2);
28 mv_fox 264
      }
265
    }
1661 mateusz.vi 266
    key = mdr_dos_getkey();
28 mv_fox 267
    if (key == 0x0D) { /* ENTER */
268
      return(res);
269
    } else if (key == 0x148) { /* up */
1948 mateusz.vi 270
      UP_AGAIN:
33 mv_fox 271
      if (res > 0) {
272
        res--;
273
        if (res < offset) offset = res;
1948 mateusz.vi 274
        if (list[res][0] == 0) goto UP_AGAIN;
33 mv_fox 275
      }
28 mv_fox 276
    } else if (key == 0x150) { /* down */
1948 mateusz.vi 277
      DOWN_AGAIN:
33 mv_fox 278
      if (res+1 < count) {
279
        res++;
1666 mateusz.vi 280
        if (res > offset + height - 1) offset = res - (height - 1);
1948 mateusz.vi 281
        if (list[res][0] == 0) goto DOWN_AGAIN;
33 mv_fox 282
      }
283
    } else if (key == 0x147) { /* home */
284
      res = 0;
285
      offset = 0;
286
    } else if (key == 0x14F) { /* end */
287
      res = count - 1;
1666 mateusz.vi 288
      if (res > offset + height - 1) offset = res - (height - 1);
28 mv_fox 289
    } else if (key == 0x1B) {  /* ESC */
290
      return(-1);
78 mv_fox 291
    }/* else {
33 mv_fox 292
      char buf[8];
55 mv_fox 293
      snprintf(buf, sizeof(buf), "0x%02X ", key);
1664 mateusz.vi 294
      video_putstring(1, 0, COLOR_BODY, buf, -1);
78 mv_fox 295
    }*/
28 mv_fox 296
  }
297
}
298
 
1946 mateusz.vi 299
 
1662 mateusz.vi 300
static void newscreen(unsigned char statusbartype) {
624 mateuszvis 301
  const char *msg;
1664 mateusz.vi 302
  mdr_cout_cls(COLOR_BODY);
624 mateuszvis 303
  msg = svarlang_strid(0x00); /* "SVARDOS INSTALLATION" */
1664 mateusz.vi 304
  mdr_cout_char_rep(0, 0, ' ', COLOR_TITLEBAR, 80);
305
  mdr_cout_str(0, 40 - (strlen(msg) >> 1), msg, COLOR_TITLEBAR, 80);
1671 mateusz.vi 306
  mdr_cout_str(0, 80 - strlen(BUILDSTRING), BUILDSTRING, COLOR_TITLEVER, 12);
307
 
79 mv_fox 308
  switch (statusbartype) {
309
    case 1:
624 mateuszvis 310
      msg = svarlang_strid(0x000B); /* "Up/Down = Select entry | Enter = Validate your choice | ESC = Quit to DOS" */
79 mv_fox 311
      break;
312
    case 2:
624 mateuszvis 313
      msg = svarlang_strid(0x0005); /* "Press any key..." */
79 mv_fox 314
      break;
315
    case 3:
316
      msg = "";
317
      break;
318
    default:
624 mateuszvis 319
      msg = svarlang_strid(0x000A); /* "Up/Down = Select entry | Enter = Validate your choice | ESC = Previous screen" */
79 mv_fox 320
      break;
321
  }
1664 mateusz.vi 322
  mdr_cout_char(24, 0, ' ', COLOR_TITLEBAR);
323
  video_putstringfix(24, 1, COLOR_TITLEBAR, msg, 79);
1662 mateusz.vi 324
  mdr_cout_locate(25,0);
28 mv_fox 325
}
326
 
1908 mateusz.vi 327
 
96 mv_fox 328
/* fills a slocales struct accordingly to the value of its keyboff member */
329
static void kblay2slocal(struct slocales *locales) {
624 mateuszvis 330
  const char *m;
96 mv_fox 331
  for (m = kblayouts[locales->keyboff]; *m != 0; m++); /* skip layout name */
332
  m++;
333
  /* skip keyb code and copy it to locales.keybcode */
334
  locales->keybcode = m;
335
  for (; *m != 0; m++);
336
  /* */
337
  locales->codepage = ((unsigned short)m[1] << 8) | m[2];
338
  locales->egafile = m[3];
339
  locales->keybfile = m[4];
340
  locales->keybid = ((unsigned short)m[5] << 8) | m[6];
1908 mateusz.vi 341
  locales->countryid = ((unsigned short)m[7] << 8) | m[8];
96 mv_fox 342
}
343
 
1908 mateusz.vi 344
 
67 mv_fox 345
static int selectlang(struct slocales *locales) {
346
  int choice, x;
624 mateuszvis 347
  const char *msg;
348
  const char *langlist[] = {
67 mv_fox 349
    "English",
1177 mateusz.vi 350
    "Brazilian",
67 mv_fox 351
    "French",
133 mv_fox 352
    "German",
119 mv_fox 353
    "Italian",
67 mv_fox 354
    "Polish",
116 mv_fox 355
    "Russian",
123 mv_fox 356
    "Slovene",
128 mv_fox 357
    "Swedish",
67 mv_fox 358
    "Turkish",
28 mv_fox 359
    NULL
360
  };
361
 
79 mv_fox 362
  newscreen(1);
624 mateuszvis 363
  msg = svarlang_strid(0x0100); /* "Welcome to SvarDOS" */
42 mv_fox 364
  x = 40 - (strlen(msg) >> 1);
1664 mateusz.vi 365
  mdr_cout_str(4, x, msg, COLOR_BODY, 80);
366
  mdr_cout_char_rep(5, x, '=', COLOR_BODY, strlen(msg));
1662 mateusz.vi 367
 
368
  /* center out the string "Please select your language..." */
369
  msg = svarlang_str(1, 1); /* "Please select your language from the list below:" */
370
  if (strlen(msg) > 74) {
1664 mateusz.vi 371
    putstringwrap(8, 1, COLOR_BODY, msg);
1662 mateusz.vi 372
  } else {
1664 mateusz.vi 373
    mdr_cout_str(8, 40 - (strlen(msg) / 2), msg, COLOR_BODY, 80);
1662 mateusz.vi 374
  }
375
 
1666 mateusz.vi 376
  choice = menuselect(11, 9, langlist, -1);
73 mv_fox 377
  if (choice < 0) return(MENUPREV);
1669 mateusz.vi 378
 
67 mv_fox 379
  /* populate locales with default values */
380
  memset(locales, 0, sizeof(struct slocales));
381
  switch (choice) {
382
    case 1:
1179 mateusz.vi 383
      strcpy(locales->lang, "BR");
384
      locales->keyboff = OFFLOC_BR;
385
      locales->keyblen = OFFLEN_BR;
386
      break;
387
    case 2:
67 mv_fox 388
      strcpy(locales->lang, "FR");
389
      locales->keyboff = OFFLOC_FR;
390
      locales->keyblen = OFFLEN_FR;
391
      break;
1177 mateusz.vi 392
    case 3:
133 mv_fox 393
      strcpy(locales->lang, "DE");
394
      locales->keyboff = OFFLOC_DE;
395
      locales->keyblen = OFFLEN_DE;
396
      break;
1177 mateusz.vi 397
    case 4:
119 mv_fox 398
      strcpy(locales->lang, "IT");
399
      locales->keyboff = OFFLOC_IT;
400
      locales->keyblen = OFFLEN_IT;
401
      break;
1177 mateusz.vi 402
    case 5:
67 mv_fox 403
      strcpy(locales->lang, "PL");
404
      locales->keyboff = OFFLOC_PL;
405
      locales->keyblen = OFFLEN_PL;
406
      break;
1177 mateusz.vi 407
    case 6:
116 mv_fox 408
      strcpy(locales->lang, "RU");
409
      locales->keyboff = OFFLOC_RU;
410
      locales->keyblen = OFFLEN_RU;
411
      break;
1177 mateusz.vi 412
    case 7:
123 mv_fox 413
      strcpy(locales->lang, "SI");
414
      locales->keyboff = OFFLOC_SI;
415
      locales->keyblen = OFFLEN_SI;
416
      break;
1177 mateusz.vi 417
    case 8:
128 mv_fox 418
      strcpy(locales->lang, "SV");
419
      locales->keyboff = OFFLOC_SV;
420
      locales->keyblen = OFFLEN_SV;
421
      break;
1177 mateusz.vi 422
    case 9:
67 mv_fox 423
      strcpy(locales->lang, "TR");
424
      locales->keyboff = OFFLOC_TR;
425
      locales->keyblen = OFFLEN_TR;
426
      break;
427
    default:
428
      strcpy(locales->lang, "EN");
429
      locales->keyboff = 0;
430
      locales->keyblen = OFFCOUNT;
431
      break;
432
  }
96 mv_fox 433
  /* populate the slocales struct accordingly to the keyboff member */
434
  kblay2slocal(locales);
67 mv_fox 435
  /* */
73 mv_fox 436
  return(MENUNEXT);
28 mv_fox 437
}
438
 
439
 
67 mv_fox 440
static int selectkeyb(struct slocales *locales) {
96 mv_fox 441
  int menuheight, choice;
442
  if (locales->keyblen == 1) return(MENUNEXT); /* do not ask for keyboard layout if only one is available for given language */
79 mv_fox 443
  newscreen(0);
1664 mateusz.vi 444
  putstringnls(5, 1, COLOR_BODY, 1, 5); /* "SvarDOS supports different keyboard layouts */
1666 mateusz.vi 445
  menuheight = locales->keyblen;
446
  if (menuheight > 11) menuheight = 11;
1665 mateusz.vi 447
  choice = menuselect(10, menuheight, &(kblayouts[locales->keyboff]), locales->keyblen);
96 mv_fox 448
  if (choice < 0) return(MENUPREV);
449
  /* (re)load the keyboard layout & codepage setup */
450
  locales->keyboff += choice;
451
  kblay2slocal(locales);
73 mv_fox 452
  return(MENUNEXT);
67 mv_fox 453
}
454
 
455
 
28 mv_fox 456
/* returns 0 if installation must proceed, non-zero otherwise */
457
static int welcomescreen(void) {
73 mv_fox 458
  int c;
624 mateuszvis 459
  const char *choice[3];
460
  choice[0] = svarlang_strid(0x0001);
461
  choice[1] = svarlang_strid(0x0002);
462
  choice[2] = NULL;
79 mv_fox 463
  newscreen(0);
1664 mateusz.vi 464
  putstringnls(4, 1, COLOR_BODY, 2, 0); /* "You are about to install SvarDOS */
1666 mateusz.vi 465
  c = menuselect(13, 2, choice, -1);
73 mv_fox 466
  if (c < 0) return(MENUPREV);
467
  if (c == 0) return(MENUNEXT);
468
  return(MENUQUIT);
28 mv_fox 469
}
470
 
471
 
1908 mateusz.vi 472
/* returns total disk space of drive drv (in MiB, max 2048, A=1 B=2 etc), or -1 if drive invalid */
1911 mateusz.vi 473
static int disksize(unsigned char drv) {
474
  unsigned short sec_per_cluster = 0;
475
  unsigned short tot_clusters = 0;
476
  unsigned short bytes_per_sec = 0;
477
  long res;
478
  _asm {
479
    push ax
480
    push bx
481
    push cx
482
    push dx
35 mv_fox 483
 
1911 mateusz.vi 484
    mov ah, 0x36
485
    mov dl, drv
486
    int 0x21
487
    /* AX=sec_per_cluster DX=tot_clusters BX=free_clusters CX=bytes_per_sec */
488
    mov sec_per_cluster, ax
489
    mov bytes_per_sec, cx
490
    mov tot_clusters, dx
35 mv_fox 491
 
1911 mateusz.vi 492
    pop dx
493
    pop cx
494
    pop bx
495
    pop ax
496
  }
497
 
498
  if (sec_per_cluster == 0xffff) return(-1);
499
  res = sec_per_cluster;
500
  res *= tot_clusters;
501
  res *= bytes_per_sec;
502
  res >>= 20;
503
  return((int)res);
504
}
505
 
506
 
35 mv_fox 507
/* returns 0 if disk is empty, non-zero otherwise */
1942 mateusz.vi 508
static int diskempty(char drv) {
35 mv_fox 509
  unsigned int rc;
510
  int res;
511
  char buff[8];
512
  struct find_t fileinfo;
1942 mateusz.vi 513
  snprintf(buff, sizeof(buff), "%c:\\*.*", drv);
35 mv_fox 514
  rc = _dos_findfirst(buff, _A_NORMAL | _A_SUBDIR | _A_HIDDEN | _A_SYSTEM, &fileinfo);
515
  if (rc == 0) {
516
    res = 1; /* call successfull means disk is not empty */
28 mv_fox 517
  } else {
35 mv_fox 518
    res = 0;
28 mv_fox 519
  }
35 mv_fox 520
  /* _dos_findclose(&fileinfo); */ /* apparently required only on OS/2 */
28 mv_fox 521
  return(res);
522
}
523
 
200 mateuszvis 524
 
1930 mateusz.vi 525
/* replace all occurences of char a by char b in s */
526
static void strtr(char *s, char a, char b) {
527
  for (;*s != 0; s++) {
528
    if (*s == a) *s = b;
529
  }
530
}
531
 
532
 
898 mateusz.vi 533
/* tries to write an empty file to drive.
534
 * asks DOS to inhibit the int 24h handler for this job, so erros are
535
 * gracefully reported - unfortunately this does not work under FreeDOS because
536
 * the DOS-C kernel does not implement the required flag.
537
 * returns 0 on success (ie. "disk exists and is writeable"). */
1908 mateusz.vi 538
static unsigned short test_drive_write(char drive) {
898 mateusz.vi 539
  unsigned short result = 0;
1908 mateusz.vi 540
  char *buff = "@:\\SVWRTEST.123";
541
  buff[0] = drive;
898 mateusz.vi 542
  _asm {
543
    push ax
544
    push bx
545
    push cx
546
    push dx
547
 
548
    mov ah, 0x6c   /* extended open/create file */
549
    mov bx, 0x2001 /* open for write + inhibit int 24h handler */
550
    xor cx, cx     /* file attributes on the created file */
551
    mov dx, 0x0010 /* create file if does not exist, fail if it exists */
552
    mov si, buff   /* filename to create */
553
    int 0x21
554
    jc FAILURE
555
    /* close the file (handle in AX) */
556
    mov bx, ax
557
    mov ah, 0x3e /* close file */
558
    int 0x21
559
    jc FAILURE
560
    /* delete the file */
561
    mov ah, 0x41 /* delete file pointed out by DS:DX */
562
    mov dx, buff
563
    int 0x21
564
    jnc DONE
565
 
566
    FAILURE:
567
    mov result, ax
568
    DONE:
569
 
570
    pop dx
571
    pop cx
572
    pop bx
573
    pop ax
574
  }
575
  return(result);
576
}
577
 
578
 
1944 mateusz.vi 579
#define MBR_WRITE 3
580
#define MBR_READ 2
581
/* read (action=2) or write (action=3) MBR of driveid (0x80, 0x81..) into buff */
582
static int READ_OR_WRITE_MBR(unsigned char action, char *buff, unsigned char driveid);
583
#pragma aux READ_OR_WRITE_MBR = \
1939 mateusz.vi 584
"push es" \
1944 mateusz.vi 585
"mov al, 1"       /* read 1 sector into memory */ \
1940 mateusz.vi 586
"mov cx, 0x0001"  /* cylinder 0, sector 1 */ \
587
"xor dh, dh"      /* head 0 */ \
1939 mateusz.vi 588
"push ds" \
589
"pop es" \
590
"int 0x13" \
591
"mov ax, 0" /* do not do "xor ax, ax", CF needs to be preserved */ \
592
"jnc DONE" \
593
"inc ax" \
594
"DONE:" \
595
"pop es" \
1944 mateusz.vi 596
parm [ah] [bx] [dl] \
1940 mateusz.vi 597
modify [cx dx] \
1939 mateusz.vi 598
value [ax]
599
 
600
 
1944 mateusz.vi 601
 
1939 mateusz.vi 602
/*
603
 * MBR structure:
604
 *
605
 * Boot signature (word 0xAA55) is at 0x01FE
606
 *
607
 * partition entry 1 is at 0x01BE
608
 *                 2    at 0x01CE
609
 *                 3    at 0x01DE
610
 *                 4    at 0x01EE
611
 *
612
 * a partition entry is:
613
 * 0x00  status byte (active is bit 0x80 set)
614
 * 0x01  CHS addr of first sector (here: HEAD)
615
 * 0x02  CHS addr of first sector (CCSSSSSS) sector in 6 low bits, cylinder high bits in CC
616
 * 0x03  CHS addr of furst sector (here: low 8 bits of cylinder)
617
 * 0x04  Partition type:
618
        0x06/0x08 for CHS FAT16/32
619
        0x0E/0x0C for LBA FAT16/32
620
 * 0x05  CHS addr of last sector (same format as for 1st sector)
621
 * 0x06  CHS addr of last sector (same format as for 1st sector)
622
 * 0x07  CHS addr of last sector (same format as for 1st sector)
623
 * 0x08  LBA addr of first sector (4 bytes) - used sometimes instead of CHS
624
 * 0x0C  number of sectors in partition (4 bytes)
625
 */
626
 
627
struct drivelist {
1940 mateusz.vi 628
  unsigned long start_lba;
1943 mateusz.vi 629
  unsigned long tot_sect;  /* size (of partition), in sectors */
1940 mateusz.vi 630
  unsigned short tot_size; /* size in MiB */
1943 mateusz.vi 631
  unsigned char partid;    /* position of the partition in the MBR (0,1,2,3) */
1944 mateusz.vi 632
  unsigned char hd;        /* 0-3 */
633
  unsigned char dosid;     /* DOS drive id (A=0, B=1...)*/
1939 mateusz.vi 634
};
635
 
1940 mateusz.vi 636
 
637
/* a (very partial) struct mimicking what EDR-DOS uses in int 2Fh,AX=0803h */
638
#pragma pack (push)
639
#pragma pack (0) /* disable the automatic alignment of struct members */
640
struct dos_udsc {
1944 mateusz.vi 641
  unsigned short next_off; /* offset FFFFh if last */
1940 mateusz.vi 642
  unsigned short next_seg;
1944 mateusz.vi 643
  unsigned char hd;        /* physical unit (as in int 13h) */
644
  unsigned char letter;    /* DOS letter drive (A=0, B=1, C=2, ...) */
1940 mateusz.vi 645
  unsigned short sectsize; /* bytes per sector */
646
  unsigned char unknown[15];
647
  unsigned long start_lba; /* LBA address of the partition (only for primary partitions) */
648
};
649
#pragma pack (pop)
650
 
651
 
652
/* get the DOS drive data table list */
653
static struct dos_udsc far *get_dos_udsc(void)  {
654
  unsigned short udsc_seg = 0, udsc_off = 0;
655
  _asm {
656
    push ds
657
    push di
658
 
659
    mov ax, 0x0803
660
    int 0x2f
661
    /* drive data table list is in DS:DI now */
662
    mov udsc_seg, ds
663
    mov udsc_off, di
664
 
665
    pop di
666
    pop ds
667
  }
668
  if (udsc_off == 0xffff) return(NULL);
669
  return(MK_FP(udsc_seg, udsc_off));
670
}
671
 
672
 
673
/* reads the MBR and matches its entries to DOS drives
1943 mateusz.vi 674
 * fills the drives array with up to 16 drives (4 partitions * 4 disks)
1940 mateusz.vi 675
 * see https://github.com/SvarDOS/bugz/issues/89 */
1943 mateusz.vi 676
static int get_drives_list(char *buff, struct drivelist *drives) {
1939 mateusz.vi 677
  int i;
1943 mateusz.vi 678
  int listlen = 0;
679
  int drv;
1940 mateusz.vi 680
  struct dos_udsc far *udsc_root = get_dos_udsc();
681
  struct dos_udsc far *udsc_node;
1939 mateusz.vi 682
 
1943 mateusz.vi 683
  for (drv = 0x80; drv < 0x84; drv++) {
1940 mateusz.vi 684
 
1944 mateusz.vi 685
    if (READ_OR_WRITE_MBR(MBR_READ, buff, drv) != 0) continue;
1939 mateusz.vi 686
 
1943 mateusz.vi 687
    /* check boot signature at 0x01fe */
688
    if (*(unsigned short *)(buff + 0x01fe) != 0xAA55) continue;
1939 mateusz.vi 689
 
1943 mateusz.vi 690
    /* iterate over the 4 partitions in the MBR */
691
    for (i = 0; i < 4; i++) {
692
      unsigned char *entry;
1939 mateusz.vi 693
 
1943 mateusz.vi 694
      entry = buff + 0x01BE + (i * 16);
1939 mateusz.vi 695
 
1943 mateusz.vi 696
      /* ignore partition if fs is unknown (non-FAT) */
697
      if ((entry[4] != 0x0E) && (entry[4] != 0x0C)  /* LBA FAT */
698
       && (entry[4] != 0x01) && (entry[4] != 0x06) && (entry[4] != 0x08)) { /* CHS FAT */
699
        continue;
1939 mateusz.vi 700
      }
1943 mateusz.vi 701
      bzero(&(drives[listlen]), sizeof(struct drivelist));
1944 mateusz.vi 702
      drives[listlen].hd = drv & 3;
1943 mateusz.vi 703
      drives[listlen].partid = i;
704
      drives[listlen].start_lba = ((unsigned long *)entry)[2];
705
      drives[listlen].tot_sect = ((unsigned long *)entry)[3];
1939 mateusz.vi 706
 
1943 mateusz.vi 707
      /* now iterate over DOS drives and try to match ont to this MBR entry */
708
      udsc_node = udsc_root;
709
      while (udsc_node != NULL) {
710
        if (udsc_node->hd != drv) goto NEXT;
711
        if (udsc_node->start_lba != drives[listlen].start_lba) goto NEXT;
1939 mateusz.vi 712
 
1943 mateusz.vi 713
        /* FOUND! */
1944 mateusz.vi 714
        drives[listlen].dosid = udsc_node->letter;
1943 mateusz.vi 715
        {
716
          unsigned long sz = (drives[listlen].tot_sect * udsc_node->sectsize) >> 20;
717
          drives[listlen].tot_size = (unsigned short)sz;
718
        }
719
        listlen++;
720
        break;
721
 
722
        NEXT:
723
        if (udsc_node->next_off == 0xffff) break;
724
        udsc_node = MK_FP(udsc_node->next_seg, udsc_node->next_off);
725
      } /* iterate over UDSC nodes */
726
    } /* iterate over partition entries of the MBR */
727
  } /* iterate over BIOS disks (0x80, 0x81, ...) */
728
 
729
  return(listlen);
1939 mateusz.vi 730
}
731
 
732
 
1942 mateusz.vi 733
/* displays a list of drives and asks user to choose one for installation
1944 mateusz.vi 734
 * returns a word with bits HHPPLLLLLLLL where:
735
 * HH = hard drive id (0..3)
736
 * PP = position of the partition in MBR (0..3)
737
 * LL... = drive's DOS id (A=0, B=1, C=2, ...) */
1942 mateusz.vi 738
static int selectdrive(void) {
1935 mateusz.vi 739
  char buff[512];
1943 mateusz.vi 740
  struct drivelist drives[16];
741
  char drvlist[16][32]; /* up to 16 drives (4 partitions on 4 disks) */
742
  int i, drvlistlen;
1942 mateusz.vi 743
  const char *menulist[16];
744
  unsigned char driveid = 1; /* fdisk runs on first drive (unless USB boot) */
1935 mateusz.vi 745
 
1940 mateusz.vi 746
  /* read MBR of first HDD */
1943 mateusz.vi 747
  drvlistlen = get_drives_list(buff, drives);
1939 mateusz.vi 748
 
1942 mateusz.vi 749
  /* if no drive found - disk not partitioned? */
750
  if (drvlistlen == 0) {
751
    const char *list[4];
752
    newscreen(0);
753
    list[0] = svarlang_str(0, 3); /* Create a partition automatically */
754
    list[1] = svarlang_str(0, 4); /* Run the FDISK tool */
755
    list[2] = svarlang_str(0, 2); /* Quit to DOS */
756
    list[3] = NULL;
757
    snprintf(buff, sizeof(buff), svarlang_strid(0x0300), SVARDOS_DISK_REQ); /* "ERROR: No drive could be found. Note, that SvarDOS requires at least %d MiB of available disk space */
758
    switch (menuselect(6 + putstringwrap(4, 1, COLOR_BODY, buff), 3, list, -1)) {
759
      case 0:
760
        sprintf(buff, "FDISK /PRI:MAX %u", driveid);
761
        exec(buff);
762
        break;
763
      case 1:
764
        mdr_cout_cls(0x07);
765
        mdr_cout_locate(0, 0);
766
        sprintf(buff, "FDISK %u", driveid);
767
        exec(buff);
768
        break;
769
      case 2:
770
        return(MENUQUIT);
771
      default:
772
        return(-1);
1908 mateusz.vi 773
    }
1942 mateusz.vi 774
    /* write a temporary MBR which only skips the drive (in case BIOS would
775
     * try to boot off the not-yet-ready C: disk) */
776
    sprintf(buff, "FDISK /LOADIPL %u", driveid);
777
    exec(buff); /* writes BOOT.MBR into actual MBR */
778
    newscreen(2);
779
    putstringnls(10, 10, COLOR_BODY, 3, 1); /* "Your computer will reboot now." */
780
    putstringnls(12, 10, COLOR_BODY, 0, 5); /* "Press any key..." */
781
    mdr_dos_getkey();
782
    reboot();
783
    return(MENUQUIT);
784
  }
1908 mateusz.vi 785
 
1943 mateusz.vi 786
  /* build a menu with all drives */
787
  for (i = 0; i < drvlistlen; i++) {
1944 mateusz.vi 788
    snprintf(drvlist[i], sizeof(drvlist[0]), "%c: [%u MiB, hd%c%u]", 'A' + drives[i].dosid, drives[i].tot_size, 'a' + drives[i].hd, drives[i].partid);
1943 mateusz.vi 789
    menulist[i] = drvlist[i];
790
  }
1948 mateusz.vi 791
  menulist[i++] = "";
1942 mateusz.vi 792
  menulist[i++] = svarlang_str(0, 2); /* Quit to DOS */
793
  menulist[i] = NULL;
1943 mateusz.vi 794
 
1942 mateusz.vi 795
  newscreen(0);
1948 mateusz.vi 796
 
797
  snprintf(buff, sizeof(buff), "Select the drive where SvarDOS will be installed. You may also return to DOS and use FDISK to partition your disk. Please note that SvarDOS may be installed only on a primary partition (ie. not a logical drive).");
798
  i = menuselect(7 + putstringwrap(4, 1, COLOR_BODY, buff) /*ypos*/, 10 /*max-height*/, menulist, -1);
1942 mateusz.vi 799
  if (i < 0) {
800
    return(MENUPREV);
801
  } else if (i < drvlistlen) {
1944 mateusz.vi 802
    /* return a bitfield HHPPLLLLLLLL
803
     * HH = hard drive id (0..3)
804
     * PP = position of the partition in MBR (0..3)
805
     * LL... = drive's DOS id (A=0, B=1, C=2, ...) */
806
    return((drives[i].partid << 8) | (drives[i].hd << 10) | drives[i].dosid);
1942 mateusz.vi 807
  }
1935 mateusz.vi 808
 
1942 mateusz.vi 809
  return(MENUQUIT);
810
}
1935 mateusz.vi 811
 
1944 mateusz.vi 812
/* hd_drv is a bitfield HHPPLLLLLLLL
813
 * HH = hard drive id (0..3)
814
 * PP = position of the partition in MBR (0..3)
815
 * LL... = drive's DOS id (A=0, B=1, C=2, ...) */
1942 mateusz.vi 816
static int preparedrive(int hd_drv) {
817
  unsigned char selecteddrive;
1944 mateusz.vi 818
  unsigned char driveid, partid;
1942 mateusz.vi 819
  char cselecteddrive;
820
  int choice;
821
  char buff[512];
556 mateuszvis 822
 
1942 mateusz.vi 823
  /* decode hd and drive id from hd_drv */
1944 mateusz.vi 824
  selecteddrive = hd_drv & 0xff; /* DOS drive letter (A=0. B=1, C=2 etc) */
825
  partid = (hd_drv >> 8) & 3;    /* position of partition in MBR (0..3) */
826
  driveid = (hd_drv >> 10);      /* HDD identifier (0..3) */
556 mateuszvis 827
 
1944 mateusz.vi 828
  cselecteddrive = 'A' + selecteddrive;
1935 mateusz.vi 829
 
1942 mateusz.vi 830
  TRY_AGAIN:
1935 mateusz.vi 831
 
1942 mateusz.vi 832
  /* if not formatted, propose to format it right away (try to create a directory) */
833
  if (test_drive_write(cselecteddrive) != 0) {
834
    const char *list[3];
79 mv_fox 835
    newscreen(0);
1942 mateusz.vi 836
    snprintf(buff, sizeof(buff), svarlang_str(3, 3), cselecteddrive); /* "ERROR: Drive %c: seems to be unformated. Do you wish to format it?") */
837
    mdr_cout_str(7, 1, buff, COLOR_BODY, 80);
556 mateuszvis 838
 
1942 mateusz.vi 839
    snprintf(buff, sizeof(buff), svarlang_strid(0x0007), cselecteddrive); /* "Format drive %c:" */
840
    list[0] = buff;
841
    list[1] = svarlang_strid(0x0002); /* "Quit to DOS" */
842
    list[2] = NULL;
556 mateuszvis 843
 
1942 mateusz.vi 844
    choice = menuselect(12, 2, list, -1);
845
    if (choice < 0) return(MENUPREV);
846
    if (choice == 1) return(MENUQUIT);
847
    mdr_cout_cls(0x07);
848
    mdr_cout_locate(0, 0);
849
    snprintf(buff, sizeof(buff), "FORMAT %c: /Q /U /Z:seriously /V:SVARDOS", cselecteddrive);
850
    exec(buff);
851
    goto TRY_AGAIN;
28 mv_fox 852
  }
1942 mateusz.vi 853
 
854
  /* check total disk space */
1944 mateusz.vi 855
  if (disksize(selecteddrive+1) < SVARDOS_DISK_REQ) {
1942 mateusz.vi 856
    int y = 9;
857
    newscreen(2);
858
    snprintf(buff, sizeof(buff), svarlang_strid(0x0304), cselecteddrive, SVARDOS_DISK_REQ); /* "ERROR: Drive %c: is not big enough! SvarDOS requires a disk of at least %d MiB." */
859
    y += putstringwrap(y, 1, COLOR_BODY, buff);
860
    putstringnls(++y, 1, COLOR_BODY, 0, 5); /* "Press any key..." */
861
    mdr_dos_getkey();
862
    return(MENUPREV);
863
  }
864
 
865
  /* is the disk empty? */
866
  newscreen(0);
867
  if (diskempty(cselecteddrive) != 0) {
868
    const char *list[3];
869
    int y = 6;
870
    snprintf(buff, sizeof(buff), svarlang_strid(0x0305), cselecteddrive); /* "ERROR: Drive %c: not empty" */
871
    y += putstringwrap(y, 1, COLOR_BODY, buff);
872
 
873
    snprintf(buff, sizeof(buff), svarlang_strid(0x0007), cselecteddrive); /* "Format drive %c:" */
874
    list[0] = buff;
875
    list[1] = svarlang_strid(0x0002); /* "Quit to DOS" */
876
    list[2] = NULL;
877
 
878
    choice = menuselect(++y, 2, list, -1);
879
    if (choice < 0) return(MENUPREV);
880
    if (choice == 1) return(MENUQUIT);
881
    mdr_cout_cls(0x07);
882
    mdr_cout_locate(0, 0);
883
    snprintf(buff, sizeof(buff), "FORMAT %c: /Q /U /Z:seriously /V:SVARDOS", cselecteddrive);
884
    exec(buff);
885
    goto TRY_AGAIN;
886
  } else {
887
    /* final confirmation */
888
    const char *list[3];
889
    list[0] = svarlang_strid(0x0001); /* Install SvarDOS */
890
    list[1] = svarlang_strid(0x0002); /* Quit to DOS */
891
    list[2] = NULL;
892
    snprintf(buff, sizeof(buff), svarlang_strid(0x0306), cselecteddrive); /* "The installation of SvarDOS to %c: is about to begin." */
893
    mdr_cout_str(7, 40 - (strlen(buff) / 2), buff, COLOR_BODY, 80);
894
    choice = menuselect(10, 2, list, -1);
895
    if (choice < 0) return(MENUPREV);
896
    if (choice == 1) return(MENUQUIT);
1944 mateusz.vi 897
 
898
    /* update the disk's MBR, transfer the boot system, make sure the partition
899
     * is ACTIVE and create a TEMP directory to copy SVP files over */
1942 mateusz.vi 900
    snprintf(buff, sizeof(buff), "SYS %c: > NUL", cselecteddrive);
901
    exec(buff);
1944 mateusz.vi 902
    sprintf(buff, "FDISK /MBR %u", driveid + 1);
1942 mateusz.vi 903
    exec(buff);
1944 mateusz.vi 904
 
905
    if (READ_OR_WRITE_MBR(MBR_READ, buff, driveid | 0x80) == 0) {
906
      /* active flags for part 0,1,2,3 are at offsets 0x01BE, 0x01CE, 0x01DE, 0x01EE */
907
      unsigned char i, flag_before, changed = 0;
908
      unsigned short memoffset = 0x01BE; /* first partition flag is here */
909
      for (i = 0; i < 4; i++) {
910
        flag_before = buff[memoffset];
911
        buff[memoffset] &= 127;
912
        if (i == partid) buff[memoffset] |= 0x80;
913
        if (flag_before != buff[memoffset]) changed++;
914
        memoffset += 16; /* jump to next partition entry */
915
      }
916
      /* do I need to update the MBR? */
917
      if (changed != 0) READ_OR_WRITE_MBR(MBR_WRITE, buff, driveid | 0x80);
918
    }
919
 
920
    snprintf(buff, sizeof(buff), "%c:\\TEMP", cselecteddrive);
1942 mateusz.vi 921
    mkdir(buff);
922
    return(0);
923
  }
28 mv_fox 924
}
925
 
926
 
280 mateuszvis 927
/* generates locales-related configurations and writes them to file (this
928
 * is used to compute autoexec.bat content) */
929
static void genlocalesconf(FILE *fd, const struct slocales *locales) {
930
  if (locales == NULL) return;
931
 
932
  fprintf(fd, "SET LANG=%s\r\n", locales->lang);
933
 
934
  if (locales->egafile > 0) {
935
    fprintf(fd, "DISPLAY CON=(EGA,,1)\r\n");
936
    if (locales->egafile == 1) {
937
      fprintf(fd, "MODE CON CP PREPARE=((%u) %%DOSDIR%%\\CPI\\EGA.CPX)\r\n", locales->codepage);
938
    } else {
1908 mateusz.vi 939
      fprintf(fd, "MODE CON CP PREPARE=((%u) %%DOSDIR%%\\CPI\\EGA%u.CPX)\r\n", locales->codepage, locales->egafile);
280 mateuszvis 940
    }
941
    fprintf(fd, "MODE CON CP SELECT=%u\r\n", locales->codepage);
942
  }
943
 
944
  if (locales->keybfile > 0) {
1659 bttr 945
    fprintf(fd, "KEYB %s,%d,%%DOSDIR%%\\", locales->keybcode, locales->codepage);
280 mateuszvis 946
    if (locales->keybfile == 1) {
947
      fprintf(fd, "KEYBOARD.SYS");
948
    } else {
1908 mateusz.vi 949
      fprintf(fd, "KEYBRD%u.SYS", locales->keybfile);
280 mateuszvis 950
    }
951
    if (locales->keybid != 0) fprintf(fd, " /ID:%d", locales->keybid);
952
    fprintf(fd, "\r\n");
953
  }
954
}
955
 
956
 
1946 mateusz.vi 957
/* get the DOS "current drive" (0=A:, 1=B:, etc) */
958
static unsigned char get_cur_drive(void);
959
#pragma aux get_cur_drive = \
960
"mov ah, 0x19" /* DOS 1+ GET CURRENT DEFAULT DRIVE */ \
961
"int 0x21" \
962
modify [ah] \
963
value [al]
964
 
965
 
966
/* generates configuration files on the dest drive, this is run once system
967
 * booted successfully on the dst drive (postinst stage) */
1930 mateusz.vi 968
static void bootfilesgen(const struct slocales *locales) {
28 mv_fox 969
  char buff[128];
970
  FILE *fd;
1946 mateusz.vi 971
  unsigned char bootdrv = get_cur_drive() + 'A';
1930 mateusz.vi 972
 
973
  /****************
974
   * CONFIG.SYS ***
975
   ****************/
1946 mateusz.vi 976
  snprintf(buff, sizeof(buff), "%c:\\CONFIG.SYS", bootdrv);
53 mv_fox 977
  fd = fopen(buff, "wb");
978
  if (fd == NULL) return;
1751 mateusz.vi 979
  fprintf(fd, "; SvarDOS kernel configuration\r\n"
980
              "\r\n"
981
              "; highest allowed drive letter\r\n"
982
              "LASTDRIVE=Z\r\n"
983
              "\r\n"
984
              "; max. number of files that programs are allowed to open simultaneously\r\n"
985
              "FILES=25\r\n");
986
  fprintf(fd, "\r\n"
987
              "; XMS memory driver\r\n"
1930 mateusz.vi 988
              "DEVICE=%c:\\SVARDOS\\HIMEMX.EXE\r\n", bootdrv);
1751 mateusz.vi 989
  fprintf(fd, "\r\n"
990
              "; try moving DOS to upper memory, then to high memory\r\n"
991
              "DOS=UMB,HIGH\r\n");
992
  fprintf(fd, "\r\n"
1930 mateusz.vi 993
              "; command interpreter (shell) location and environment size\r\n"
994
              "SHELL=%c:\\COMMAND.COM /E:512 /P\r\n", bootdrv);
1751 mateusz.vi 995
  fprintf(fd, "\r\n"
1908 mateusz.vi 996
              "; NLS configuration\r\n");
997
  if (locales != NULL) {
1930 mateusz.vi 998
    fprintf(fd, "COUNTRY=%03u,%u,%c:\\SVARDOS\\COUNTRY.SYS\r\n", locales->countryid, locales->codepage, bootdrv);
1908 mateusz.vi 999
  } else {
1930 mateusz.vi 1000
    fprintf(fd, "COUNTRY=001,437,%c:\\SVARDOS\\COUNTRY.SYS\r\n", bootdrv);
1908 mateusz.vi 1001
  }
1751 mateusz.vi 1002
  fprintf(fd, "\r\n"
1003
              "; CD-ROM driver initialization\r\n"
1930 mateusz.vi 1004
              ";DEVICE=%c:\\DRIVERS\\VIDECDD\\VIDE-CDD.SYS /D:SVCD0001\r\n", bootdrv);
53 mv_fox 1005
  fclose(fd);
1930 mateusz.vi 1006
 
1007
  /****************
1008
   * AUTOEXEC.BAT *
1009
   ****************/
1010
  snprintf(buff, sizeof(buff), "%c:\\TEMP\\AUTOEXEC.BAT", bootdrv);
28 mv_fox 1011
  fd = fopen(buff, "wb");
1930 mateusz.vi 1012
  if (fd == NULL) {
1013
    return;
1014
  } else {
1015
    char *autoexec_bat1 =
1016
      "@ECHO OFF\r\n"
1935 mateusz.vi 1017
      "SET TEMP=#:\\TEMP\r\n"
1018
      "SET DOSDIR=#:\\SVARDOS\r\n"
1930 mateusz.vi 1019
      "SET NLSPATH=%DOSDIR%\\NLS\r\n"
1020
      "SET DIRCMD=/O/P\r\n"
1946 mateusz.vi 1021
      "SET WATTCP.CFG=%DOSDIR%\r\n"
1930 mateusz.vi 1022
      "PATH %DOSDIR%\r\n"
1023
      "PROMPT $P$G\r\n"
1024
      "\r\n"
1025
      "REM enable CPU power saving\r\n"
1026
      "FDAPM ADV:REG\r\n"
1027
      "\r\n";
1028
    char *autoexec_bat2 =
1029
      "REM Uncomment the line below for CDROM support\r\n"
1030
      "REM SHSUCDX /d:SVCD0001\r\n"
1031
      "\r\n"
1032
      "ECHO.\r\n";
1033
 
1935 mateusz.vi 1034
    /* replace all '#' occurences by bootdrive */
1035
    strtr(autoexec_bat1, '#', bootdrv);
1930 mateusz.vi 1036
 
1037
    /* write all to file */
1038
    fputs(autoexec_bat1, fd);
1039
    if (locales != NULL) genlocalesconf(fd, locales);
1040
    fputs(autoexec_bat2, fd);
1041
 
1042
    fprintf(fd, "ECHO %s\r\n", svarlang_strid(0x0600)); /* "Welcome to SvarDOS!" */
1043
    fclose(fd);
1044
  }
1045
 
200 mateuszvis 1046
  /*** CREATE DIRECTORY FOR CONFIGURATION FILES ***/
1930 mateusz.vi 1047
  snprintf(buff, sizeof(buff), "%c:\\SVARDOS", bootdrv);
200 mateuszvis 1048
  mkdir(buff);
1930 mateusz.vi 1049
 
1050
  /****************
1051
   * PKG.CFG      *
1052
   ****************/
1935 mateusz.vi 1053
  snprintf(buff, sizeof(buff), "%c:\\SVARDOS\\PKG.CFG", bootdrv);
277 mateuszvis 1054
  fd = fopen(buff, "wb");
1930 mateusz.vi 1055
  if (fd == NULL) {
1056
    return;
1057
  } else {
1058
    char *pkg_cfg =
1059
      "# pkg config file - specifies locations where packages should be installed\r\n"
1060
      "\r\n"
1061
      "# System boot drive\r\n"
1935 mateusz.vi 1062
      "BOOTDRIVE @\r\n"
1930 mateusz.vi 1063
      "\r\n"
1064
      "# DOS core binaries\r\n"
1065
      "DIR BIN @:\\SVARDOS\r\n"
1066
      "\r\n"
1067
      "# Programs\r\n"
1068
      "DIR PROGS @:\\\r\n"
1069
      "\r\n"
1070
      "# Games \r\n"
1071
      "DIR GAMES @:\\\r\n"
1072
      "\r\n"
1073
      "# Drivers\r\n"
1074
      "DIR DRIVERS @:\\DRIVERS\r\n"
1075
      "\r\n"
1076
      "# Development tools\r\n"
1077
      "DIR DEVEL @:\\DEVEL\r\n";
1078
 
1079
    /* replace all @ by the actual boot drive */
1080
    strtr(pkg_cfg, '@', bootdrv);
1081
 
1082
    /* write to file */
1083
    fputs(pkg_cfg, fd);
1084
    fclose(fd);
1085
  }
1086
 
1946 mateusz.vi 1087
  /****************
1088
   * PICOTCP      *
1089
   ****************/
1930 mateusz.vi 1090
  /* TODO (or not? maybe not that useful) */
1091
 
1946 mateusz.vi 1092
  /****************
1093
   * WATTCP.CFG   *
1094
   ****************/
1095
  snprintf(buff, sizeof(buff), "%c:\\SVARDOS\\WATTCP.CFG", bootdrv);
303 mateuszvis 1096
  fd = fopen(buff, "wb");
1097
  if (fd == NULL) return;
1098
  fprintf(fd, "my_ip = dhcp\r\n"
1099
              "#my_ip = 192.168.0.7\r\n"
1100
              "#netmask = 255.255.255.0\r\n"
1101
              "#nameserver = 192.168.0.1\r\n"
1102
              "#nameserver = 192.168.0.2\r\n"
554 mateuszvis 1103
              "#gateway = 192.168.0.1\r\n");
303 mateuszvis 1104
  fclose(fd);
1946 mateusz.vi 1105
 
1106
  /****************
1107
   * POSTINST.BAT *
1108
   ****************/
1109
  /* create the postinst.bat file for actual installation of packages */
1110
  snprintf(buff, sizeof(buff), "POSTINST.BAT");
1111
  fd = fopen(buff, "wb");
1112
  if (fd == NULL) {
1113
    return;
1114
  }
1115
  fprintf(fd,
1116
    "@ECHO OFF\r\n"
1117
    "SET DOSDIR=%c:\\SVARDOS\r\n"
1118
    "PATH %%DOSDIR%%\r\n"
1119
    "ECHO INSTALLING PACKAGES\r\n"
1120
    "COPY \\COMMAND.COM \\CMD.COM\r\n" /* move COMMAND.COM so it does not clashes with the installation of the SVARCOM package */
1121
    "SET COMSPEC=%c:\\CMD.COM\r\n"
1122
    "DEL \\AUTOEXEC.BAT\r\n"
1123
    "COPY AUTOEXEC.BAT \\\r\n"
1124
    "DEL \\COMMAND.COM\r\n"
1125
    "DEL \\KERNEL.SYS\r\n" /* KERNEL.SYS will be installed from the package in a moment */
1126
    "FOR %%%%P IN (*.SVP) DO PKG INSTALL %%%%P\r\n" /* install packages */
1127
    "DEL *.SVP\r\n", bootdrv, bootdrv);
1128
 
1129
  /* restore COMSPEC and do some cleanup */
1130
  fprintf(fd, "DEL pkg.exe\r\n"
1131
              "DEL install.com\r\n"
1132
              "DEL install.lng\r\n"
1133
              "SET COMSPEC=\\COMMAND.COM\r\n"
1134
              "DEL \\CMD.COM\r\n");
1135
  /* print out the "installation over" message (load codepage first, now that MODE is installed) */
1136
  genlocalesconf(fd, locales);
1137
  fprintf(fd, "ECHO.\r\n"
1138
              "ECHO ");
1139
  fprintf(fd, svarlang_strid(0x0502)); /* "SvarDOS installation is over. Please restart your computer now" */
1140
  fprintf(fd, "\r\n"
1141
              "ECHO.\r\n");
1142
  fclose(fd);
1143
 
28 mv_fox 1144
}
1145
 
1146
 
1942 mateusz.vi 1147
static int copypackages(char drvletter, const struct slocales *locales) {
192 mateuszvis 1148
  char pkglist[512];
30 mv_fox 1149
  int i, pkglistlen;
192 mateuszvis 1150
  size_t pkglistflen;
280 mateuszvis 1151
  char buff[1024]; /* must be *at least* 1 sector big for efficient file copying */
1152
  FILE *fd = NULL;
192 mateuszvis 1153
  char *pkgptr;
1942 mateusz.vi 1154
 
79 mv_fox 1155
  newscreen(3);
1942 mateusz.vi 1156
 
192 mateuszvis 1157
  /* load pkg list */
1158
  fd = fopen("install.lst", "rb");
1159
  if (fd == NULL) {
1664 mateusz.vi 1160
    mdr_cout_str(10, 30, "ERROR: INSTALL.LST NOT FOUND", COLOR_BODY, 80);
1661 mateusz.vi 1161
    mdr_dos_getkey();
192 mateuszvis 1162
    return(-1);
1163
  }
1125 mateusz.vi 1164
  pkglistflen = fread(pkglist, 1, sizeof(pkglist) - 2, fd);
192 mateuszvis 1165
  fclose(fd);
1125 mateusz.vi 1166
  if (pkglistflen == sizeof(pkglist) - 2) {
1664 mateusz.vi 1167
    mdr_cout_str(10, 30, "ERROR: INSTALL.LST TOO LARGE", COLOR_BODY, 80);
1661 mateusz.vi 1168
    mdr_dos_getkey();
192 mateuszvis 1169
    return(-1);
1170
  }
1125 mateusz.vi 1171
  /* mark the end of list */
1172
  pkglist[pkglistflen] = 0;
1173
  pkglist[pkglistflen + 1] = 0xff;
192 mateuszvis 1174
  /* replace all \r and \n chars by 0 bytes, and count the number of packages */
1175
  pkglistlen = 0;
1176
  for (i = 0; i < pkglistflen; i++) {
1177
    switch (pkglist[i]) {
1178
      case '\n':
1179
        pkglistlen++;
1180
        /* FALLTHRU */
1181
      case '\r':
1182
        pkglist[i] = 0;
1183
        break;
1184
    }
1185
  }
280 mateuszvis 1186
 
1930 mateusz.vi 1187
  /* copy pkg.exe, install.com and install.lng to the new drive, along with all packages */
1942 mateusz.vi 1188
  snprintf(buff, sizeof(buff), "%c:\\TEMP\\PKG.EXE", drvletter);
1930 mateusz.vi 1189
  fcopy(buff, buff + 8, buff, sizeof(buff));
1942 mateusz.vi 1190
  snprintf(buff, sizeof(buff), "%c:\\TEMP\\INSTALL.COM", drvletter);
1930 mateusz.vi 1191
  fcopy(buff, buff + 8, buff, sizeof(buff));
1942 mateusz.vi 1192
  snprintf(buff, sizeof(buff), "%c:\\TEMP\\INSTALL.LNG", drvletter);
1930 mateusz.vi 1193
  fcopy(buff, buff + 8, buff, sizeof(buff));
1194
 
280 mateuszvis 1195
  /* copy packages */
192 mateuszvis 1196
  for (i = 0;; i++) {
1125 mateusz.vi 1197
    RETRY_ENTIRE_LIST:
1198
 
192 mateuszvis 1199
    /* move forward to nearest entry or end of list */
1125 mateusz.vi 1200
    for (pkgptr = pkglist; *pkgptr == 0; pkgptr++);
1201
    if (*pkgptr == 0xff) break; /* end of list: means all packages have been processed */
1202
 
1203
    /* is this package present on the floppy disk? */
1204
    TRY_NEXTPKG:
1205
    sprintf(buff, "%s.svp", pkgptr);
1669 mateusz.vi 1206
    if (!fileexists(buff)) {
1125 mateusz.vi 1207
      while (*pkgptr != 0) pkgptr++;
1208
      while (*pkgptr == 0) pkgptr++;
1209
      /* end of list? ask for next floppy, there's nothing interesting left on this one */
1210
      if (*pkgptr == 0xff) {
1664 mateusz.vi 1211
        putstringnls(12, 1, COLOR_BODY, 4, 1); /* "INSERT THE DISK THAT CONTAINS THE REQUIRED FILE AND PRESS ANY KEY" */
1661 mateusz.vi 1212
        mdr_dos_getkey();
1664 mateusz.vi 1213
        video_putstringfix(12, 1, COLOR_BODY, "", 80); /* erase the 'insert disk' message */
1125 mateusz.vi 1214
        goto RETRY_ENTIRE_LIST;
1215
      }
1216
      goto TRY_NEXTPKG;
1217
    }
1218
 
192 mateuszvis 1219
    /* install the package */
624 mateuszvis 1220
    snprintf(buff, sizeof(buff), svarlang_strid(0x0400), i+1, pkglistlen, pkgptr); /* "Installing package %d/%d: %s" */
36 mv_fox 1221
    strcat(buff, "       ");
1664 mateusz.vi 1222
    mdr_cout_str(10, 1, buff, COLOR_BODY, 40);
1125 mateusz.vi 1223
 
1224
    /* proceed with package copy */
1942 mateusz.vi 1225
    sprintf(buff, "%c:\\TEMP\\%s.svp", drvletter, pkgptr);
1930 mateusz.vi 1226
    if (fcopy(buff, buff + 8, buff, sizeof(buff)) != 0) {
1664 mateusz.vi 1227
      mdr_cout_str(10, 30, "READ ERROR", COLOR_BODY, 80);
1661 mateusz.vi 1228
      mdr_dos_getkey();
192 mateuszvis 1229
      return(-1);
55 mv_fox 1230
    }
1125 mateusz.vi 1231
    /* jump to next entry or end of list and zero out the pkg name in the process */
1232
    while ((*pkgptr != 0) && (*pkgptr != 0xff)) {
1233
      *pkgptr = 0;
1234
      pkgptr++;
1235
    }
28 mv_fox 1236
  }
1934 mateusz.vi 1237
 
1946 mateusz.vi 1238
  /* create an empty postinst.bat file so installer knows it is 2nd stage */
1942 mateusz.vi 1239
  snprintf(buff, sizeof(buff), "%c:\\TEMP\\POSTINST.BAT", drvletter);
1934 mateusz.vi 1240
  fd = fopen(buff, "wb");
1241
  if (fd == NULL) return(-1);
280 mateuszvis 1242
  fclose(fd);
1243
 
1946 mateusz.vi 1244
  /* prepare a dummy autoexec.bat that will exec install and call temp\postinst.bat */
1942 mateusz.vi 1245
  snprintf(buff, sizeof(buff), "%c:\\autoexec.bat", drvletter);
280 mateuszvis 1246
  fd = fopen(buff, "wb");
1247
  if (fd == NULL) return(-1);
1248
  fprintf(fd, "@ECHO OFF\r\n"
1659 bttr 1249
              "PATH %%DOSDIR%%\r\n");
280 mateuszvis 1250
  fprintf(fd, "CD TEMP\r\n"
1946 mateusz.vi 1251
              "install\r\n"   /* installer will run in 2nd stage (generating autoexec.bat, pkg.cfg and stuff) */
280 mateuszvis 1252
              "postinst.bat\r\n");
1253
  fclose(fd);
1254
 
192 mateuszvis 1255
  return(0);
28 mv_fox 1256
}
1257
 
1258
 
42 mv_fox 1259
static void finalreboot(void) {
56 mv_fox 1260
  int y = 9;
79 mv_fox 1261
  newscreen(2);
1673 mateusz.vi 1262
  y += putstringnls(y, 1, COLOR_BODY, 5, 0); /* "Your computer will reboot now." */
1263
  y += putstringnls(y, 1, COLOR_BODYWARN, 5, 1); /* Please remove the installation disk from your drive" */
1664 mateusz.vi 1264
  putstringnls(++y, 1, COLOR_BODY, 0, 5); /* "Press any key..." */
1661 mateusz.vi 1265
  mdr_dos_getkey();
42 mv_fox 1266
  reboot();
1267
}
1268
 
1269
 
192 mateuszvis 1270
static void loadcp(const struct slocales *locales) {
42 mv_fox 1271
  char buff[64];
67 mv_fox 1272
  if (locales->codepage == 437) return;
1662 mateusz.vi 1273
  mdr_cout_locate(1, 0);
67 mv_fox 1274
  if (locales->egafile == 1) {
310 mateuszvis 1275
    snprintf(buff, sizeof(buff), "MODE CON CP PREP=((%u) EGA.CPX) > NUL", locales->codepage);
42 mv_fox 1276
  } else {
310 mateuszvis 1277
    snprintf(buff, sizeof(buff), "MODE CON CP PREP=((%u) EGA%d.CPX) > NUL", locales->codepage, locales->egafile);
42 mv_fox 1278
  }
1918 mateusz.vi 1279
  exec(buff);
67 mv_fox 1280
  snprintf(buff, sizeof(buff), "MODE CON CP SEL=%u > NUL", locales->codepage);
1918 mateusz.vi 1281
  exec(buff);
42 mv_fox 1282
  /* below I re-init the video controller - apparently this is required if
65 mv_fox 1283
   * I want the new glyph symbols to be actually applied, at least some
1284
   * (broken?) BIOSes, like VBox, apply glyphs only at next video mode change */
1908 mateusz.vi 1285
  _asm {
1286
    push bx
1287
    mov ah, 0x0F  /* get current video mode */
1288
    int 0x10      /* al contains the current video mode now */
1289
    or al, 128    /* set high bit of AL to instruct BIOS not to flush VRAM's content (EGA+) */
1290
    xor ah, ah    /* re-set video mode (to whatever is set in AL) */
1291
    int 0x10
1292
    pop bx
42 mv_fox 1293
  }
1294
}
1295
 
200 mateuszvis 1296
 
1671 mateusz.vi 1297
int main(void) {
1908 mateusz.vi 1298
  struct slocales locales_data;
1299
  struct slocales *locales = &locales_data;
28 mv_fox 1300
  int targetdrv;
73 mv_fox 1301
  int action;
28 mv_fox 1302
 
1911 mateusz.vi 1303
  /* setup an internal int 24h handler ("always fail") so DOS does not output
1304
   * the ugly "abort, retry, fail" messages */
1918 mateusz.vi 1305
  install_int24();
908 mateusz.vi 1306
 
1930 mateusz.vi 1307
  /* init screen and detect mono adapters */
1308
  if (mdr_cout_init(NULL, NULL) == 0) {
1309
    /* overload color scheme with mono settings */
1310
    COLOR_TITLEBAR = 0x70;
1311
    COLOR_TITLEVER = 0x70;
1312
    COLOR_BODY = 0x07;
1313
    COLOR_BODYWARN = 0x07;
1314
    COLOR_SELECT = 0x70;
1315
    COLOR_SELECTCUR = 0x07;
1316
  }
1317
 
1318
  /* is it stage 2 of the installation? */
1319
  if (fileexists("postinst.bat")) goto GENCONF;
1320
 
1671 mateusz.vi 1321
  /* read the svardos build revision (from floppy label) */
1322
  {
1323
    const char *fspec = "*.*";
1324
    const char *res = (void*)0x9E; /* default DTA is at PSP:80h, field 1Eh of DTA is the ASCIZ file name */
868 mateusz.vi 1325
 
1671 mateusz.vi 1326
    _asm {
1327
      push cx
1328
      push dx
1329
 
1330
      mov ax, 0x4e00  /* findfirst */
1331
      mov cx, 0x08    /* file attr mask, 0x08 = volume label */
1332
      mov dx, fspec
1333
      int 0x21
1334
      jnc good
1672 mateusz.vi 1335
      mov bx, res
1336
      mov [bx], byte ptr 0
1671 mateusz.vi 1337
      good:
1338
 
1339
      pop dx
1340
      pop cx
1341
    }
1342
 
1343
    memcpy(BUILDSTRING, res, 12);
1344
  }
1345
 
1908 mateusz.vi 1346
  /* am I EN-only? */
1347
  if (!fileexists("INSTALL.LNG")) locales = NULL;
1348
 
73 mv_fox 1349
 SelectLang:
1908 mateusz.vi 1350
  if (locales == NULL) goto WelcomeScreen;
1351
  action = selectlang(locales); /* welcome to svardos, select your language */
1930 mateusz.vi 1352
  if (action != MENUNEXT) goto QUIT;
1908 mateusz.vi 1353
  loadcp(locales);
1354
  svarlang_load("INSTALL.LNG", locales->lang); /* NLS support */
865 mateusz.vi 1355
 
1908 mateusz.vi 1356
  action = selectkeyb(locales);  /* what keyb layout should we use? */
1930 mateusz.vi 1357
  if (action == MENUQUIT) goto QUIT;
1908 mateusz.vi 1358
  if (action == MENUPREV) goto SelectLang;
73 mv_fox 1359
 
1360
 WelcomeScreen:
190 mateuszvis 1361
  action = welcomescreen(); /* what svardos is, ask whether to run live dos or install */
1930 mateusz.vi 1362
  if (action == MENUQUIT) goto QUIT;
1908 mateusz.vi 1363
  if (action == MENUPREV) {
1930 mateusz.vi 1364
    if (locales == NULL) goto QUIT;
1908 mateusz.vi 1365
    goto SelectLang;
1366
  }
1669 mateusz.vi 1367
 
1942 mateusz.vi 1368
 SelDriveScreen:
1369
  targetdrv = selectdrive();
1930 mateusz.vi 1370
  if (targetdrv == MENUQUIT) goto QUIT;
73 mv_fox 1371
  if (targetdrv == MENUPREV) goto WelcomeScreen;
1942 mateusz.vi 1372
 
1373
  action = preparedrive(targetdrv); /* what drive should we install to? check avail. space */
1374
  if (action == MENUQUIT) goto QUIT;
1375
  if (action == MENUPREV) goto SelDriveScreen;
1944 mateusz.vi 1376
  targetdrv = (targetdrv & 0xff) + 'A'; /* convert the part+hd+drv value to a DOS letter */
1942 mateusz.vi 1377
 
1378
  /* copy packages to dst drive */
1379
  if (copypackages(targetdrv, locales) != 0) goto QUIT;
1930 mateusz.vi 1380
  finalreboot(); /* remove the install medium and reboot */
73 mv_fox 1381
 
1930 mateusz.vi 1382
  goto QUIT;
1383
 
1384
 GENCONF: /* second stage of the installation (run from the destination disk) */
1385
  bootfilesgen(locales); /* generate boot files and other configurations */
1386
 
1387
 QUIT:
1662 mateusz.vi 1388
  mdr_cout_locate(0, 0);
1389
  mdr_cout_close();
28 mv_fox 1390
  return(0);
1391
}