Subversion Repositories SvarDOS

Rev

Rev 2223 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
421 mateuszvis 1
/* This file is part of the SvarCOM project and is published under the terms
2
 * of the MIT license.
3
 *
1629 mateusz.vi 4
 * Copyright (C) 2021-2024 Mateusz Viste
421 mateuszvis 5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a
7
 * copy of this software and associated documentation files (the "Software"),
8
 * to deal in the Software without restriction, including without limitation
9
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
 * and/or sell copies of the Software, and to permit persons to whom the
11
 * Software is furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22
 * DEALINGS IN THE SOFTWARE.
23
 */
24
 
352 mateuszvis 25
/*
26
 * a variety of helper functions
27
 */
28
 
396 mateuszvis 29
#include <i86.h>    /* MK_FP() */
30
 
968 mateusz.vi 31
#include "svarlang.lib\svarlang.h"
32
 
437 mateuszvis 33
#include "env.h"
1881 mateusz.vi 34
#include "rmodinit.h"
437 mateuszvis 35
 
352 mateuszvis 36
#include "helpers.h"
37
 
420 mateuszvis 38
 
965 mateusz.vi 39
 
1823 mateusz.vi 40
void dos_get_date(unsigned short *y, unsigned char *m, unsigned char *d) {
41
  /* get cur date */
42
  _asm {
43
    mov ah, 0x2a  /* DOS 1+ -- Query DOS Date */
44
    int 0x21      /* CX=year DH=month DL=day */
45
    mov bx, y
46
    mov [bx], cx
47
    mov bx, m
48
    mov [bx], dh
49
    mov bx, d
50
    mov [bx], dl
51
  }
52
}
53
 
54
 
55
void dos_get_time(unsigned char *h, unsigned char *m, unsigned char *s) {
56
  _asm {
57
    mov ah, 0x2c  /* DOS 1+ -- Query DOS Time */
58
    int 0x21      /* CH=hour CL=minutes DH=seconds DL=1/100sec */
59
    mov bx, h
60
    mov [bx], ch
61
    mov bx, m
62
    mov [bx], cl
63
    mov bx, s
64
    mov [bx], dh
65
  }
66
}
67
 
68
 
2213 mateusz.vi 69
/* like strlen() */
70
unsigned short sv_strlen(const char *s) {
71
  unsigned short len = 0;
72
  while (*s != 0) {
73
    s++;
74
    len++;
75
  }
76
  return(len);
77
}
78
 
79
 
530 mateuszvis 80
/* case-insensitive comparison of strings, compares up to maxlen characters.
81
 * returns non-zero on equality. */
82
int imatchlim(const char *s1, const char *s2, unsigned short maxlen) {
83
  while (maxlen--) {
352 mateuszvis 84
    char c1, c2;
85
    c1 = *s1;
86
    c2 = *s2;
87
    if ((c1 >= 'a') && (c1 <= 'z')) c1 -= ('a' - 'A');
88
    if ((c2 >= 'a') && (c2 <= 'z')) c2 -= ('a' - 'A');
89
    /* */
90
    if (c1 != c2) return(0);
530 mateuszvis 91
    if (c1 == 0) break;
352 mateuszvis 92
    s1++;
93
    s2++;
94
  }
530 mateuszvis 95
  return(1);
352 mateuszvis 96
}
97
 
98
 
99
/* returns zero if s1 starts with s2 */
100
int strstartswith(const char *s1, const char *s2) {
101
  while (*s2 != 0) {
102
    if (*s1 != *s2) return(-1);
103
    s1++;
104
    s2++;
105
  }
106
  return(0);
107
}
369 mateuszvis 108
 
109
 
538 mateuszvis 110
/* outputs a NULL-terminated string to handle (1=stdout 2=stderr) */
111
void output_internal(const char *s, unsigned char nl, unsigned char handle) {
112
  const static unsigned char *crlf = "\r\n";
369 mateuszvis 113
  _asm {
445 mateuszvis 114
    push ds
115
    pop es         /* make sure es=ds (scasb uses es) */
116
    /* get length of s into CX */
117
    mov ax, 0x4000 /* ah=DOS "write to file" and AL=0 for NULL matching */
118
    mov dx, s      /* set dx to string (required for later) */
119
    mov di, dx     /* set di to string (for NULL matching) */
120
    mov cx, 0xffff /* preset cx to 65535 (-1) */
121
    cld            /* clear DF so scasb increments DI */
122
    repne scasb    /* cmp al, es:[di], inc di, dec cx until match found */
123
    /* CX contains (65535 - strlen(s)) now */
124
    not cx         /* reverse all bits so I get (strlen(s) + 1) */
125
    dec cx         /* this is CX length */
126
    jz WRITEDONE   /* do nothing for empty strings */
127
 
128
    /* output by writing to stdout */
129
    /* mov ah, 0x40 */  /* DOS 2+ -- write to file via handle */
538 mateuszvis 130
    xor bh, bh
131
    mov bl, handle /* set handle (1=stdout 2=stderr) */
445 mateuszvis 132
    /* mov cx, xxx */ /* write CX bytes */
133
    /* mov dx, s   */ /* DS:DX is the source of bytes to "write" */
369 mateuszvis 134
    int 0x21
445 mateuszvis 135
    WRITEDONE:
136
 
137
    /* print out a CR/LF trailer if nl set */
538 mateuszvis 138
    test byte ptr [nl], 0xff
369 mateuszvis 139
    jz FINITO
538 mateuszvis 140
    /* bx still contains handle */
141
    mov ah, 0x40 /* "write to file" */
142
    mov cx, 2
445 mateuszvis 143
    mov dx, crlf
369 mateuszvis 144
    int 0x21
145
    FINITO:
146
  }
147
}
388 mateuszvis 148
 
149
 
542 mateuszvis 150
void nls_output_internal(unsigned short id, unsigned char nl, unsigned char handle) {
538 mateuszvis 151
  const char *NOTFOUND = "NLS_STRING_NOT_FOUND";
968 mateusz.vi 152
  const char *ptr = svarlang_strid(id);
985 mateusz.vi 153
  if ((ptr == NULL) || (ptr[0]) == 0) ptr = NOTFOUND;
542 mateuszvis 154
  output_internal(ptr, nl, handle);
538 mateuszvis 155
}
156
 
157
 
959 mateusz.vi 158
/* output DOS error e to stdout, if stdout is redirected then *additionally*
159
 * also to stderr */
538 mateuszvis 160
void nls_outputnl_doserr(unsigned short e) {
161
  static char errstr[16];
162
  const char *ptr = NULL;
959 mateusz.vi 163
  unsigned char redirflag = 0;
538 mateuszvis 164
  /* find string in nls block */
968 mateusz.vi 165
  if (e < 0xff) ptr = svarlang_strid(0xff00 | e);
538 mateuszvis 166
  /* if not found, use a fallback */
1046 mateusz.vi 167
  if ((ptr == NULL) || (ptr[0] == 0)) {
2220 mateusz.vi 168
    sv_strcpy(errstr, "DOS ERR ");
169
    ustoa(errstr + sv_strlen(errstr), e, 0, '0');
538 mateuszvis 170
    ptr = errstr;
171
  }
959 mateusz.vi 172
 
173
  /* display to stdout */
174
  output_internal(ptr, 1, hSTDOUT);
175
 
176
  /* is stdout redirected? */
177
  _asm {
178
    push bx
179
    push dx
180
 
181
    mov ax, 0x4400   /* query device flags */
182
    mov bx, 1        /* stdout */
183
    int 0x21
184
    /* CF set on error and AX filled with DOS error,
185
     * returns flags in DX on succes:
186
     *  bit 7 reset if handle points to a file, set if handle points to a device  */
187
    jc FAIL
188
    mov redirflag, dl
189
    and redirflag, 128
190
 
191
    FAIL:
192
    pop dx
193
    pop bx
194
  }
195
 
196
  if (redirflag == 0) output_internal(ptr, 1, hSTDERR);
538 mateuszvis 197
}
198
 
199
 
388 mateuszvis 200
/* find first matching files using a FindFirst DOS call
201
 * returns 0 on success or a DOS err code on failure */
202
unsigned short findfirst(struct DTA *dta, const char *pattern, unsigned short attr) {
203
  unsigned short res = 0;
204
  _asm {
205
    /* set DTA location */
206
    mov ah, 0x1a
207
    mov dx, dta
208
    int 0x21
209
    /* */
210
    mov ah, 0x4e    /* FindFirst */
211
    mov dx, pattern
212
    mov cx, attr
213
    int 0x21        /* CF set on error + err code in AX, DTA filled with FileInfoRec on success */
214
    jnc DONE
215
    mov [res], ax
216
    DONE:
217
  }
218
  return(res);
219
}
220
 
221
 
222
/* find next matching, ie. continues an action intiated by findfirst() */
223
unsigned short findnext(struct DTA *dta) {
224
  unsigned short res = 0;
225
  _asm {
2199 mateusz.vi 226
    /* set DTA location */
227
    mov ah, 0x1a
388 mateuszvis 228
    mov dx, dta
2199 mateusz.vi 229
    int 0x21
230
    /* FindNext */
231
    mov ah, 0x4f
388 mateuszvis 232
    int 0x21        /* CF set on error + err code in AX, DTA filled with FileInfoRec on success */
233
    jnc DONE
234
    mov [res], ax
235
    DONE:
236
  }
237
  return(res);
238
}
392 mateuszvis 239
 
240
 
1997 mateusz.vi 241
static unsigned char _dos_getkey_noecho(void);
242
#pragma aux _dos_getkey_noecho = \
243
"mov ax, 0x0c08" /* clear input buffer and execute getchar (INT 21h,AH=8) */  \
244
"int 0x21"                                                                    \
245
"test al, al"    /* if AL == 0 then this is an extended character */          \
246
"jnz GOTCHAR"                                                                 \
247
"mov ah, 0x08"   /* read again to flush extended char from input buffer */    \
248
"int 0x21"                                                                    \
249
"xor al, al"     /* all extended chars are ignored */                         \
250
"GOTCHAR:"       /* received key is in AL now */                              \
251
modify [ah]                                                                   \
252
value [al]
253
 
254
 
392 mateuszvis 255
/* print s string and wait for a single key press from stdin. accepts only
256
 * key presses defined in the c ASCIIZ string. returns offset of pressed key
1997 mateusz.vi 257
 * in string. keys in c MUST BE UPPERCASE! ENTER chooses the FIRST choice */
392 mateuszvis 258
unsigned short askchoice(const char *s, const char *c) {
259
  unsigned short res;
1001 mateusz.vi 260
  char cstr[2] = {0,0};
392 mateuszvis 261
  char key = 0;
262
 
263
  output(s);
264
  output(" ");
1001 mateusz.vi 265
  output("(");
266
  for (res = 0; c[res] != 0; res++) {
267
    if (res != 0) output("/");
268
    cstr[0] = c[res];
269
    output(cstr);
270
  }
271
  output(") ");
392 mateuszvis 272
 
1997 mateusz.vi 273
  AGAIN:
274
  key = _dos_getkey_noecho();
275
  if (key == '\r') key = c[0]; /* ENTER is synonym for the first key */
392 mateuszvis 276
 
277
  /* ucase() result */
278
  if ((key >= 'a') && (key <= 'z')) key -= ('a' - 'A');
279
 
280
  /* is there a match? */
1997 mateusz.vi 281
  for (res = 0; c[res] != 0; res++) {
282
    if (c[res] == key) {
283
      cstr[0] = key;
284
      output(cstr);
285
      output("\r\n");
286
      return(res);
287
    }
288
  }
392 mateuszvis 289
 
290
  goto AGAIN;
291
}
292
 
293
 
399 mateuszvis 294
/* converts a path to its canonic representation, returns 0 on success
295
 * or DOS err on failure (invalid drive) */
296
unsigned short file_truename(const char *src, char *dst) {
297
  unsigned short res = 0;
392 mateuszvis 298
  _asm {
399 mateuszvis 299
    push es
392 mateuszvis 300
    mov ah, 0x60  /* query truename, DS:SI=src, ES:DI=dst */
301
    push ds
302
    pop es
303
    mov si, src
304
    mov di, dst
305
    int 0x21
399 mateuszvis 306
    jnc DONE
307
    mov [res], ax
308
    DONE:
309
    pop es
392 mateuszvis 310
  }
399 mateuszvis 311
  return(res);
392 mateuszvis 312
}
313
 
314
 
315
/* returns DOS attributes of file, or -1 on error */
316
int file_getattr(const char *fname) {
317
  int res = -1;
318
  _asm {
319
    mov ax, 0x4300  /* query file attributes, fname at DS:DX */
320
    mov dx, fname
321
    int 0x21        /* CX=attributes if CF=0, otherwise AX=errno */
322
    jc DONE
323
    mov [res], cx
324
    DONE:
325
  }
326
  return(res);
327
}
396 mateuszvis 328
 
329
 
330
/* returns screen's width (in columns) */
331
unsigned short screen_getwidth(void) {
332
  /* BIOS 0040:004A = word containing screen width in text columns */
333
  unsigned short far *scrw = MK_FP(0x40, 0x4a);
334
  return(*scrw);
335
}
336
 
337
 
338
/* returns screen's height (in rows) */
339
unsigned short screen_getheight(void) {
340
  /* BIOS 0040:0084 = byte containing maximum valid row value (EGA ONLY) */
341
  unsigned char far *scrh = MK_FP(0x40, 0x84);
342
  if (*scrh == 0) return(25);  /* pre-EGA adapter */
343
  return(*scrh + 1);
344
}
345
 
346
 
347
/* displays the "Press any key to continue" msg and waits for a keypress */
348
void press_any_key(void) {
437 mateuszvis 349
  nls_output(15, 1); /* Press any key to continue... */
396 mateuszvis 350
  _asm {
351
    mov ah, 0x08  /* no echo console input */
352
    int 0x21      /* pressed key in AL now (0 for extended keys) */
353
    test al, al
354
    jnz DONE
355
    int 0x21      /* executed ah=8 again to read the rest of extended key */
356
    DONE:
357
    /* output CR/LF */
358
    mov ah, 0x02
359
    mov dl, 0x0D
360
    int 0x21
361
    mov dl, 0x0A
362
    int 0x21
363
  }
364
}
399 mateuszvis 365
 
366
 
367
/* validate a drive (A=0, B=1, etc). returns 1 if valid, 0 otherwise */
368
int isdrivevalid(unsigned char drv) {
369
  _asm {
370
    mov ah, 0x19  /* query default (current) disk */
371
    int 0x21      /* drive in AL (0=A, 1=B, etc) */
372
    mov ch, al    /* save current drive to ch */
373
    /* try setting up the drive as current */
374
    mov ah, 0x0E   /* select default drive */
375
    mov dl, [drv]  /* 0=A, 1=B, etc */
376
    int 0x21
377
    /* this call does not set CF on error, I must check cur drive to look for success */
378
    mov ah, 0x19  /* query default (current) disk */
379
    int 0x21      /* drive in AL (0=A, 1=B, etc) */
380
    mov [drv], 1  /* preset result as success */
381
    cmp al, dl    /* is eq? */
382
    je DONE
383
    mov [drv], 0  /* fail */
384
    jmp FAILED
385
    DONE:
386
    /* set current drive back to what it was initially */
387
    mov ah, 0x0E
388
    mov dl, ch
389
    int 0x21
390
    FAILED:
391
  }
392
  return(drv);
393
}
406 mateuszvis 394
 
395
 
396
/* converts a 8+3 filename into 11-bytes FCB format (MYFILE  EXT) */
397
void file_fname2fcb(char *dst, const char *src) {
398
  unsigned short i;
399
 
400
  /* fill dst with 11 spaces and a NULL terminator */
420 mateuszvis 401
  for (i = 0; i < 11; i++) dst[i] = ' ';
402
  dst[11] = 0;
406 mateuszvis 403
 
404
  /* copy fname until dot (.) or 8 characters */
405
  for (i = 0; i < 8; i++) {
406
    if ((src[i] == '.') || (src[i] == 0)) break;
407
    dst[i] = src[i];
408
  }
409
 
410
  /* advance src until extension or end of string */
411
  src += i;
412
  for (;;) {
413
    if (*src == '.') {
414
      src++; /* next character is extension */
415
      break;
416
    }
417
    if (*src == 0) break;
418
  }
419
 
420
  /* copy extension to dst (3 chars max) */
421
  dst += 8;
422
  for (i = 0; i < 3; i++) {
423
    if (src[i] == 0) break;
424
    dst[i] = src[i];
425
  }
426
}
427
 
428
 
429
/* converts a 11-bytes FCB filename (MYFILE  EXT) into 8+3 format (MYFILE.EXT) */
430
void file_fcb2fname(char *dst, const char *src) {
431
  unsigned short i, end = 0;
432
 
433
  for (i = 0; i < 8; i++) {
434
    dst[i] = src[i];
435
    if (dst[i] != ' ') end = i + 1;
436
  }
437
 
438
  /* is there an extension? */
439
  if (src[8] == ' ') {
440
    dst[end] = 0;
441
  } else { /* found extension: copy it until first space */
442
    dst[end++] = '.';
443
    for (i = 8; i < 11; i++) {
444
      if (src[i] == ' ') break;
445
      dst[end++] = src[i];
446
    }
447
    dst[end] = 0;
448
  }
449
}
410 mateuszvis 450
 
451
 
2223 mateusz.vi 452
/* convert an unsigned short to ASCIZ, output expanded to minlen chars
453
 * (prefixed with prefixchar if value too small, else truncated)
2220 mateusz.vi 454
 * returns length of produced string */
2223 mateusz.vi 455
unsigned short ustoa(char *dst, unsigned short n, unsigned char minlen, char prefixchar) {
2220 mateusz.vi 456
  unsigned short r;
2223 mateusz.vi 457
  unsigned char i, len;
2220 mateusz.vi 458
  unsigned char nonzerocharat = 5;
459
 
460
  for (i = 4; i != 0xff; i--) {
461
    r = n % 10;
462
    n /= 10;
463
    dst[i] = '0' + r;
464
    if (r != 0) nonzerocharat = i;
465
  }
466
 
467
  /* change prefix to prefixchar (eg. "00120" -> "  120") */
468
  for (i = 0; i < 4; i++) {
469
    if (dst[i] != '0') break;
470
    dst[i] = prefixchar;
471
  }
472
 
2223 mateusz.vi 473
  len = 5 - nonzerocharat;
474
 
475
  /* apply minlen, if set */
476
  if ((minlen > len) && (minlen < 6)) {
477
    len = minlen;
478
    nonzerocharat = 5 - minlen;
2220 mateusz.vi 479
  }
480
 
481
  if (nonzerocharat != 0) {
2223 mateusz.vi 482
    memcpy_ltr(dst, dst + nonzerocharat, len);
2220 mateusz.vi 483
  }
484
 
2223 mateusz.vi 485
  dst[len] = 0;
2220 mateusz.vi 486
 
2223 mateusz.vi 487
  return(len);
2220 mateusz.vi 488
}
489
 
490
 
2222 mateusz.vi 491
/* converts an unsigned short to a four-byte ASCIZ hex string ("0ABC") */
492
void ustoh(char *dst, unsigned short n) {
493
  const char *h = "0123456789ABCDEF";
494
  dst[2] = h[(n >> 4) & 15];
495
  dst[3] = h[n & 15];
496
  n >>= 8;
497
  dst[0] = h[n >> 4];
498
  dst[1] = h[n & 15];
499
}
500
 
501
 
430 mateuszvis 502
/* converts an ASCIIZ string into an unsigned short. returns 0 on success.
503
 * on error, result will contain all valid digits that were read until
504
 * error occurred (0 on overflow or if parsing failed immediately) */
426 mateuszvis 505
int atous(unsigned short *r, const char *s) {
410 mateuszvis 506
  int err = 0;
507
 
508
  _asm {
509
    mov si, s
510
    xor ax, ax  /* general purpose register */
511
    xor cx, cx  /* contains the result */
512
    mov bx, 10  /* used as a multiplicative step */
513
 
514
    NEXTBYTE:
515
    xchg cx, ax /* move result into cx temporarily */
516
    lodsb  /* AL = DS:[SI++] */
517
    /* is AL 0? if so we're done */
518
    test al, al
519
    jz DONE
520
    /* validate that AL is in range '0'-'9' */
521
    sub al, '0'
430 mateuszvis 522
    jc FAIL   /* invalid character detected */
410 mateuszvis 523
    cmp al, 9
430 mateuszvis 524
    jg FAIL   /* invalid character detected */
410 mateuszvis 525
    /* restore result into AX (CX contains the new digit) */
526
    xchg cx, ax
527
    /* multiply result by 10 and add cl */
528
    mul bx    /* DX AX = AX * BX(10) */
430 mateuszvis 529
    jc OVERFLOW  /* overflow */
410 mateuszvis 530
    add ax, cx
430 mateuszvis 531
    /* if CF is set then overflow occurred (overflow part lands in DX) */
410 mateuszvis 532
    jnc NEXTBYTE
533
 
430 mateuszvis 534
    OVERFLOW:
535
    xor cx, cx  /* make sure result is zeroed in case overflow occured */
536
 
410 mateuszvis 537
    FAIL:
538
    inc [err]
539
 
540
    DONE: /* save result (CX) into indirect memory address r */
541
    mov bx, [r]
542
    mov [bx], cx
543
  }
544
  return(err);
545
}
415 mateuszvis 546
 
547
 
548
/* appends a backslash if path is a directory
549
 * returns the (possibly updated) length of path */
550
unsigned short path_appendbkslash_if_dir(char *path) {
551
  unsigned short len;
552
  int attr;
553
  for (len = 0; path[len] != 0; len++);
554
  if (len == 0) return(0);
555
  if (path[len - 1] == '\\') return(len);
556
  /* */
557
  attr = file_getattr(path);
558
  if ((attr > 0) && (attr & DOS_ATTR_DIR)) {
559
    path[len++] = '\\';
560
    path[len] = 0;
561
  }
562
  return(len);
563
}
416 mateuszvis 564
 
565
 
566
/* get current path drive d (A=1, B=2, etc - 0 is "current drive")
567
 * returns 0 on success, doserr otherwise */
568
unsigned short curpathfordrv(char *buff, unsigned char d) {
569
  unsigned short r = 0;
570
 
571
  _asm {
572
    /* is d == 0? then I need to resolve current drive */
573
    cmp byte ptr [d], 0
574
    jne GETCWD
575
    /* resolve cur drive */
576
    mov ah, 0x19  /* get current default drive */
577
    int 0x21      /* al = drive (00h = A:, 01h = B:, etc) */
578
    inc al        /* convert to 1=A, 2=B, etc */
579
    mov [d], al
580
 
581
    GETCWD:
582
    /* prepend buff with drive:\ */
583
    mov si, buff
584
    mov dl, [d]
585
    mov [si], dl
586
    add byte ptr [si], 'A' - 1
587
    inc si
588
    mov [si], ':'
589
    inc si
590
    mov [si], '\\'
591
    inc si
592
 
593
    mov ah, 0x47      /* get current directory of drv DL into DS:SI */
594
    int 0x21
595
    jnc DONE
596
    mov [r], ax       /* copy result from ax */
597
 
598
    DONE:
599
  }
600
 
601
  return(r);
602
}
420 mateuszvis 603
 
604
 
2213 mateusz.vi 605
/* like strcpy() but returns the string's length */
606
unsigned short sv_strcpy(char *dst, const char *s) {
607
  unsigned short len = 0;
608
  for (;;) {
609
    *dst = *s;
610
    if (*s == 0) return(len);
611
    dst++;
612
    s++;
613
    len++;
614
  }
615
}
616
 
617
 
618
/* like sv_strcpy() but operates on far pointers */
619
unsigned short sv_strcpy_far(char far *dst, const char far *s) {
620
  unsigned short len = 0;
621
  for (;;) {
622
    *dst = *s;
623
    if (*s == 0) return(len);
624
    dst++;
625
    s++;
626
    len++;
627
  }
628
}
629
 
630
 
631
/* like strcat() */
632
void sv_strcat(char *dst, const char *s) {
633
  /* advance dst to end of string */
634
  while (*dst != 0) dst++;
635
  /* append string */
636
  sv_strcpy(dst, s);
637
}
638
 
639
 
640
/* like strcat() but operates on far pointers */
641
void sv_strcat_far(char far *dst, const char far *s) {
642
  /* advance dst to end of string */
643
  while (*dst != 0) dst++;
644
  /* append string */
645
  sv_strcpy_far(dst, s);
646
}
647
 
648
 
420 mateuszvis 649
/* fills a nls_patterns struct with current NLS patterns, returns 0 on success, DOS errcode otherwise */
650
unsigned short nls_getpatterns(struct nls_patterns *p) {
651
  unsigned short r = 0;
652
 
653
  _asm {
654
    mov ax, 0x3800  /* DOS 2+ -- Get Country Info for current country */
655
    mov dx, p       /* DS:DX points to the CountryInfoRec buffer */
656
    int 0x21
657
    jnc DONE
658
    mov [r], ax     /* copy DOS err code to r */
659
    DONE:
660
  }
661
 
662
  return(r);
663
}
664
 
665
 
666
/* computes a formatted date based on NLS patterns found in p
667
 * returns length of result */
668
unsigned short nls_format_date(char *s, unsigned short yr, unsigned char mo, unsigned char dy, const struct nls_patterns *p) {
669
  unsigned short items[3];
2220 mateusz.vi 670
  unsigned short slen;
671
 
420 mateuszvis 672
  /* preset date/month/year in proper order depending on date format */
673
  switch (p->dateformat) {
674
    case 0:  /* USA style: m d y */
675
      items[0] = mo;
676
      items[1] = dy;
677
      items[2] = yr;
678
      break;
679
    case 1:  /* EU style: d m y */
680
      items[0] = dy;
681
      items[1] = mo;
682
      items[2] = yr;
683
      break;
684
    case 2:  /* Japan-style: y m d */
685
    default:
686
      items[0] = yr;
687
      items[1] = mo;
688
      items[2] = dy;
689
      break;
690
  }
691
  /* compute the string */
2220 mateusz.vi 692
 
693
  slen = ustoa(s, items[0], 2, '0');
694
  slen += sv_strcpy(s + slen, p->datesep);
695
  slen += ustoa(s + slen, items[1], 2, '0');
696
  slen += sv_strcpy(s + slen, p->datesep);
697
  slen += ustoa(s + slen, items[2], 2, '0');
698
 
699
  return(slen);
420 mateuszvis 700
}
701
 
702
 
426 mateuszvis 703
/* computes a formatted time based on NLS patterns found in p, sc are ignored if set 0xff
420 mateuszvis 704
 * returns length of result */
426 mateuszvis 705
unsigned short nls_format_time(char *s, unsigned char ho, unsigned char mn, unsigned char sc, const struct nls_patterns *p) {
706
  char ampm = 0;
707
  unsigned short res;
708
 
420 mateuszvis 709
  if (p->timefmt == 0) {
710
    if (ho == 12) {
426 mateuszvis 711
      ampm = 'p';
420 mateuszvis 712
    } else if (ho > 12) {
713
      ho -= 12;
426 mateuszvis 714
      ampm = 'p';
420 mateuszvis 715
    } else { /* ho < 12 */
716
      if (ho == 0) ho = 12;
426 mateuszvis 717
      ampm = 'a';
420 mateuszvis 718
    }
2220 mateusz.vi 719
    res = ustoa(s, ho, 2, ' '); /* %2u */
426 mateuszvis 720
  } else {
2220 mateusz.vi 721
    res = ustoa(s, ho, 2, '0'); /* %02u */
420 mateuszvis 722
  }
426 mateuszvis 723
 
724
  /* append separator and minutes */
2220 mateusz.vi 725
  res += sv_strcpy(s + res, p->timesep);
726
  res += ustoa(s + res, mn, 2, '0'); /* %02u */
426 mateuszvis 727
 
728
  /* if seconds provided, append them, too */
2220 mateusz.vi 729
  if (sc != 0xff) {
730
    res += sv_strcpy(s + res, p->timesep);
731
    res += ustoa(s + res, sc, 2, '0'); /* %02u */
732
  }
426 mateuszvis 733
 
2220 mateusz.vi 734
  /* finally append the AM/PM char */
426 mateuszvis 735
  if (ampm != 0) s[res++] = ampm;
736
  s[res] = 0;
737
 
738
  return(res);
420 mateuszvis 739
}
740
 
741
 
2245 mateusz.vi 742
/* divides an unsigned long by 10 using 16bit division */
743
static unsigned short divulong10(unsigned long *n) {
744
  unsigned short a, b;
745
  unsigned short rem;
746
  const long adj[10] = {-1, 6553, 13107, 19660, 26214, 32767, 39321, 45875, 52428, 58982};
747
 
748
  b = *n;
749
  *n >>= 16;
750
  a = *n;
751
 
752
  rem = ((a % 10) << 4) + (b % 10);
753
  rem %= 10;
754
 
755
  /* AB / 10 = (((A / 10) + ((A % 10) / 10)) << 16) + (B / 10) */
756
 
757
  *n = (a / 10);
758
  *n <<= 16;
759
  *n += b / 10;
760
 
761
  /* I have no clue about the math behind this, found out the cyclic relation
762
   * using brute force */
763
  *n += adj[a % 10];
764
  if ((b % 10) >= (((a % 10) * 4) % 10)) *n += 1;
765
 
766
  return(rem);
767
}
768
 
769
 
420 mateuszvis 770
/* computes a formatted integer number based on NLS patterns found in p
771
 * returns length of result */
423 mateuszvis 772
unsigned short nls_format_number(char *s, unsigned long num, const struct nls_patterns *p) {
773
  unsigned short sl = 0, i;
420 mateuszvis 774
  unsigned char thcount = 0;
775
 
423 mateuszvis 776
  /* write the value (reverse) with thousand separators (if any defined) */
420 mateuszvis 777
  do {
2245 mateusz.vi 778
    unsigned short rem;
420 mateuszvis 779
    if ((thcount == 3) && (p->thousep[0] != 0)) {
780
      s[sl++] = p->thousep[0];
781
      thcount = 0;
782
    }
2245 mateusz.vi 783
    rem = divulong10(&num);
784
    s[sl++] = '0' + rem;
420 mateuszvis 785
    thcount++;
786
  } while (num > 0);
787
 
423 mateuszvis 788
  /* terminate the string */
420 mateuszvis 789
  s[sl] = 0;
790
 
423 mateuszvis 791
  /* reverse the string now (has been built in reverse) */
792
  for (i = sl / 2 + (sl & 1); i < sl; i++) {
420 mateuszvis 793
    thcount = s[i];
423 mateuszvis 794
    s[i] = s[sl - (i + 1)];   /* abc'de  if i=3 then ' <-> c */
420 mateuszvis 795
    s[sl - (i + 1)] = thcount;
796
  }
797
 
423 mateuszvis 798
  return(sl);
420 mateuszvis 799
}
437 mateuszvis 800
 
801
 
1137 mateusz.vi 802
/* capitalize an ASCIZ string following country-dependent rules */
803
void nls_strtoup(char *buff) {
804
  unsigned short errcode = 0;
805
  /* requires DOS 4+ */
806
  _asm {
807
    push ax
808
    push dx
809
 
810
    mov ax, 0x6522 /* country-dependent capitalize string (DOS 4+) */
811
    mov dx, buff   /* DS:DX -> string to capitalize */
812
    int 0x21
813
    jnc DONE
814
 
815
    mov errcode, ax /* set errcode on failure */
816
    DONE:
817
 
818
    pop dx
819
    pop ax
820
  }
821
 
2213 mateusz.vi 822
  /* do a naive upcase if DOS has no NLS support */
823
  if (errcode != 0) {
824
    while (*buff) {
825
      if (*buff < 128) { /* apply only to 7bit (low ascii) values */
826
        *buff &= 0xDF;
827
      }
828
      buff++;
829
    }
830
  }
1137 mateusz.vi 831
}
832
 
833
 
1881 mateusz.vi 834
/* reload nls ressources from svarcom.lng into svarlang_mem and rmod */
835
void nls_langreload(char *buff, unsigned short rmodseg) {
1629 mateusz.vi 836
  const char far *dosdir;
968 mateusz.vi 837
  const char far *lang;
965 mateusz.vi 838
  static unsigned short lastlang;
1629 mateusz.vi 839
  unsigned short dosdirlen;
1881 mateusz.vi 840
  unsigned short rmodenvseg = *(unsigned short far *)MK_FP(rmodseg, RMOD_OFFSET_ENVSEG);
841
  unsigned char far *rmodcritmsg = MK_FP(rmodseg, RMOD_OFFSET_CRITMSG);
842
  int i;
437 mateuszvis 843
 
844
  /* look up the LANG env variable, upcase it and copy to lang */
1881 mateusz.vi 845
  lang = env_lookup_val(rmodenvseg, "LANG");
968 mateusz.vi 846
  if ((lang == NULL) || (lang[0] == 0)) return;
2213 mateusz.vi 847
  memcpy_ltr_far(buff, lang, 2);
437 mateuszvis 848
  buff[2] = 0;
849
 
850
  /* check if there is need to reload at all */
2213 mateusz.vi 851
  if (lastlang == *((unsigned short *)buff)) return;
437 mateuszvis 852
 
968 mateusz.vi 853
  buff[4] = 0;
1881 mateusz.vi 854
  dosdir = env_lookup_val(rmodenvseg, "DOSDIR");
1629 mateusz.vi 855
  if (dosdir == NULL) return;
437 mateuszvis 856
 
2213 mateusz.vi 857
  sv_strcpy_far(buff + 4, dosdir);
858
  dosdirlen = sv_strlen(buff + 4);
1629 mateusz.vi 859
  if (buff[4 + dosdirlen - 1] == '\\') dosdirlen--;
2213 mateusz.vi 860
  memcpy_ltr(buff + 4 + dosdirlen, "\\SVARCOM.LNG", 13);
437 mateuszvis 861
 
1629 mateusz.vi 862
  /* try loading %DOSDIR%\SVARCOM.LNG */
863
  if (svarlang_load(buff + 4, buff) != 0) {
1881 mateusz.vi 864
    /* failed! try %DOSDIR%\BIN\SVARCOM.LNG */
2213 mateusz.vi 865
    memcpy_ltr(buff + 4 + dosdirlen, "\\BIN\\SVARCOM.LNG", 17);
1629 mateusz.vi 866
    if (svarlang_load(buff + 4, buff) != 0) return;
867
  }
868
 
2213 mateusz.vi 869
  memcpy_ltr_far(&lastlang, lang, 2);
1881 mateusz.vi 870
 
871
  /* update RMOD's critical handler with new strings */
2161 mateusz.vi 872
  for (i = 0; i < 9; i++) {
1881 mateusz.vi 873
    int len;
2213 mateusz.vi 874
    len = sv_strlen(svarlang_str(3, i));
1881 mateusz.vi 875
    if (len > 15) len = 15;
2213 mateusz.vi 876
    memcpy_ltr_far(rmodcritmsg + (i * 16), svarlang_str(3, i), len);
877
    memcpy_ltr_far(rmodcritmsg + (i * 16) + len, "$", 1);
1881 mateusz.vi 878
  }
879
  /* The ARIF string is special: always 4 bytes long and no $ terminator */
2213 mateusz.vi 880
  memcpy_ltr_far(rmodcritmsg + (9 * 16), svarlang_str(3,9), 4);
437 mateuszvis 881
}
571 mateuszvis 882
 
883
 
884
/* locates executable fname in path and fill res with result. returns 0 on success,
885
 * -1 on failed match and -2 on failed match + "don't even try with other paths"
886
 * extptr is filled with a ptr to the extension in fname (NULL if no extension) */
887
int lookup_cmd(char *res, const char *fname, const char *path, const char **extptr) {
888
  unsigned short lastbslash = 0;
889
  unsigned short i, len;
890
  unsigned char explicitpath = 0;
1072 mateusz.vi 891
  const char *exec_ext[] = {"COM", "EXE", "BAT", NULL};
571 mateuszvis 892
 
893
  /* does the original fname has an explicit path prefix or explicit ext? */
894
  *extptr = NULL;
895
  for (i = 0; fname[i] != 0; i++) {
896
    switch (fname[i]) {
897
      case ':':
898
      case '\\':
899
        explicitpath = 1;
900
        *extptr = NULL; /* extension is the last dot AFTER all path delimiters */
901
        break;
902
      case '.':
903
        *extptr = fname + i + 1;
904
        break;
905
    }
906
  }
907
 
1072 mateusz.vi 908
  /* if explicit ext found, make sure it is executable */
909
  if (*extptr != NULL) {
910
    for (i = 0; exec_ext[i] != NULL; i++) if (imatch(*extptr, exec_ext[i])) break;
911
    if (exec_ext[i] == NULL) return(-2); /* bad extension - don't try running it ever */
912
  }
913
 
571 mateuszvis 914
  /* normalize filename */
915
  if (file_truename(fname, res) != 0) return(-2);
916
 
917
  /* printf("truename: %s\r\n", res); */
918
 
1072 mateusz.vi 919
  /* figure out where the command starts */
571 mateuszvis 920
  for (len = 0; res[len] != 0; len++) {
921
    switch (res[len]) {
922
      case '?':   /* abort on any wildcard character */
923
      case '*':
924
        return(-2);
925
      case '\\':
926
        lastbslash = len;
927
        break;
928
    }
929
  }
930
 
931
  /* printf("lastbslash=%u\r\n", lastbslash); */
932
 
933
  /* if no path prefix was found in fname (no colon or backslash) AND we have
934
   * a path arg, then assemble path+filename */
935
  if ((!explicitpath) && (path != NULL) && (path[0] != 0)) {
2213 mateusz.vi 936
    i = sv_strlen(path);
571 mateuszvis 937
    if (path[i - 1] != '\\') i++; /* add a byte for inserting a bkslash after path */
938
    /* move the filename at the place where path will end */
2213 mateusz.vi 939
    memcpy_rtl(res + i, res + lastbslash + 1, len - lastbslash);
571 mateuszvis 940
    /* copy path in front of the filename and make sure there is a bkslash sep */
2213 mateusz.vi 941
    memcpy_ltr(res, path, i);
571 mateuszvis 942
    res[i - 1] = '\\';
943
  }
944
 
945
  /* if no extension was initially provided, try matching COM, EXE, BAT */
946
  if (*extptr == NULL) {
1072 mateusz.vi 947
    int attr;
2213 mateusz.vi 948
    len = sv_strlen(res);
1072 mateusz.vi 949
    res[len++] = '.';
950
    for (i = 0; exec_ext[i] != NULL; i++) {
2213 mateusz.vi 951
      sv_strcpy(res + len, exec_ext[i]);
571 mateuszvis 952
      /* printf("? '%s'\r\n", res); */
1072 mateusz.vi 953
      *extptr = exec_ext[i];
954
      attr = file_getattr(res);
955
      if (attr < 0) continue; /* file not found */
956
      if (attr & DOS_ATTR_DIR) continue; /* this is a directory */
957
      if (attr & DOS_ATTR_VOL) continue; /* this is a volume */
958
      return(0);
571 mateuszvis 959
    }
960
  } else { /* try finding it as-is */
961
    /* printf("? '%s'\r\n", res); */
1072 mateusz.vi 962
    int attr = file_getattr(res);
963
    if ((attr >= 0) &&  /* file exists */
964
        ((attr & DOS_ATTR_DIR) == 0) && /* is not a directory */
965
        ((attr & DOS_ATTR_VOL) == 0)) { /* is not a volume */
966
      return(0);
967
    }
571 mateuszvis 968
  }
969
 
970
  /* not found */
971
  if (explicitpath) return(-2); /* don't bother trying other paths, the caller had its own path preset anyway */
972
  return(-1);
973
}
974
 
975
 
976
/* fills fname with the path and filename to the linkfile related to the
977
 * executable link "linkname". returns 0 on success. */
978
int link_computefname(char *fname, const char *linkname, unsigned short env_seg) {
1044 mateusz.vi 979
  unsigned short pathlen, doserr = 0;
571 mateuszvis 980
 
981
  /* fetch %DOSDIR% */
982
  pathlen = env_lookup_valcopy(fname, 128, env_seg, "DOSDIR");
1988 mateusz.vi 983
  if (pathlen == 0) return(-1);
571 mateuszvis 984
 
985
  /* prep filename: %DOSDIR%\LINKS\PKG.LNK */
986
  if (fname[pathlen - 1] == '\\') pathlen--;
2220 mateusz.vi 987
  pathlen += sv_strcpy(fname + pathlen, "\\LINKS");
1044 mateusz.vi 988
  /* create \LINKS if not exists */
989
  if (file_getattr(fname) < 0) {
990
    _asm {
991
      push dx
992
      mov ah, 0x39
993
      mov dx, fname
994
      int 0x21
995
      jnc DONE
996
      mov doserr, ax
997
      DONE:
998
      pop dx
999
    }
1000
    if (doserr) {
1001
      output(fname);
1002
      output(" - ");
1003
      nls_outputnl(255, doserr);
1004
      return(-1);
1005
    }
1006
  }
1007
  /* quit early if dir does not exist (or is not a dir) */
1043 mateusz.vi 1008
  if (file_getattr(fname) != DOS_ATTR_DIR) {
1009
    output(fname);
1010
    output(" - ");
1011
    nls_outputnl(255,3); /* path not found */
1012
    return(-1);
1013
  }
2220 mateusz.vi 1014
  sv_strcat(fname + pathlen, "\\");
1015
  sv_strcat(fname + pathlen, linkname);
1016
  sv_strcat(fname + pathlen, ".LNK");
571 mateuszvis 1017
 
1018
  return(0);
1019
}
2195 mateusz.vi 1020
 
1021
 
1022
/* like memcpy() but guarantees to copy from left to right */
1023
void memcpy_ltr(void *d, const void *s, unsigned short len) {
1024
  unsigned char const *ss = s;
1025
  unsigned char *dd = d;
1026
 
1027
  while (len--) {
1028
    *dd = *ss;
1029
    ss++;
1030
    dd++;
1031
  }
1032
}
1033
 
2213 mateusz.vi 1034
 
1035
/* like memcpy_ltr() but operates on far pointers */
1036
void memcpy_ltr_far(void far *d, const void far *s, unsigned short len) {
1037
  unsigned char const far *ss = s;
1038
  unsigned char far *dd = d;
1039
 
1040
  while (len--) {
1041
    *dd = *ss;
1042
    ss++;
1043
    dd++;
1044
  }
1045
}
1046
 
1047
 
2195 mateusz.vi 1048
/* like memcpy() but guarantees to copy from right to left */
1049
void memcpy_rtl(void *d, const void *s, unsigned short len) {
1050
  unsigned char const *ss = s;
1051
  unsigned char *dd = d;
1052
 
1053
  dd += len - 1;
1054
  ss += len - 1;
1055
  while (len--) {
1056
    *dd = *ss;
1057
    ss--;
1058
    dd--;
1059
  }
1060
}
2213 mateusz.vi 1061
 
1062
 
1063
/* like bzero(), but accepts far pointers */
1064
void sv_bzero(void far *dst, unsigned short len) {
1065
  char far *d = dst;
1066
  while (len--) {
1067
    *d = 0;
1068
    d++;
1069
  }
1070
}
2218 mateusz.vi 1071
 
1072
 
1073
/* like memset() */
1074
void sv_memset(void *dst, unsigned char c, unsigned short len) {
1075
  unsigned char *d = dst;
1076
  while (len--) {
1077
    *d = c;
1078
    d++;
1079
  }
1080
}
2222 mateusz.vi 1081
 
1082
 
1083
/* replaces characters a by b in s */
1084
void sv_strtr(char *s, char a, char b) {
1085
  while (*s) {
1086
    if (*s == a) *s = b;
1087
    s++;
1088
  }
1089
}
1090
 
1091
 
1092
/* inserts string s2 into s1 in place of the first % character */
1093
void sv_insert_str_in_str(char *s1, const char *s2) {
1094
  unsigned short s2len;
1095
 
1096
  /* fast forward s1 to either % or 0 */
1097
  while ((*s1 != 0) && (*s1 != '%')) s1++;
1098
 
1099
  /* if not % then quit */
1100
  if (*s1 != '%') return;
1101
 
1102
  /* make room for s2, unless s2 is exactly 1 byte long */
1103
  s2len = sv_strlen(s2);
1104
  if (s2len == 0) {
1105
    memcpy_ltr(s1, s1 + 1, sv_strlen(s1 + 1) + 1);
1106
  } else if (s2len > 1) {
1107
    memcpy_rtl(s1 + s2len, s1 + 1, sv_strlen(s1 + 1) + 1);
1108
  }
1109
 
1110
  /* write s2 to the cleared space */
1111
  if (s2len != 0) memcpy_ltr(s1, s2, s2len);
1112
}