Subversion Repositories SvarDOS

Rev

Rev 2220 | Rev 2223 | Go to most recent revision | 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
 
2220 mateusz.vi 452
/* convert an unsigned short to ASCIZ, output set to fixlen chars if fixlen
453
 * is non zero (prefixed with prefixchar if value too small, else truncated)
454
 * returns length of produced string */
455
unsigned short ustoa(char *dst, unsigned short n, unsigned char fixlen, char prefixchar) {
456
  unsigned short r;
457
  unsigned char i;
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
 
473
  /* apply fixlen, if set */
474
  if ((fixlen > 0) && (fixlen < 6)) {
475
    nonzerocharat = 5 - fixlen;
476
  }
477
 
478
  if (nonzerocharat != 0) {
479
    memcpy_ltr(dst, dst + nonzerocharat, 5 - nonzerocharat);
480
  }
481
 
482
  dst[5 - nonzerocharat] = 0;
483
 
484
  return(5 - nonzerocharat);
485
}
486
 
487
 
2222 mateusz.vi 488
/* converts an unsigned short to a four-byte ASCIZ hex string ("0ABC") */
489
void ustoh(char *dst, unsigned short n) {
490
  const char *h = "0123456789ABCDEF";
491
  dst[2] = h[(n >> 4) & 15];
492
  dst[3] = h[n & 15];
493
  n >>= 8;
494
  dst[0] = h[n >> 4];
495
  dst[1] = h[n & 15];
496
}
497
 
498
 
430 mateuszvis 499
/* converts an ASCIIZ string into an unsigned short. returns 0 on success.
500
 * on error, result will contain all valid digits that were read until
501
 * error occurred (0 on overflow or if parsing failed immediately) */
426 mateuszvis 502
int atous(unsigned short *r, const char *s) {
410 mateuszvis 503
  int err = 0;
504
 
505
  _asm {
506
    mov si, s
507
    xor ax, ax  /* general purpose register */
508
    xor cx, cx  /* contains the result */
509
    mov bx, 10  /* used as a multiplicative step */
510
 
511
    NEXTBYTE:
512
    xchg cx, ax /* move result into cx temporarily */
513
    lodsb  /* AL = DS:[SI++] */
514
    /* is AL 0? if so we're done */
515
    test al, al
516
    jz DONE
517
    /* validate that AL is in range '0'-'9' */
518
    sub al, '0'
430 mateuszvis 519
    jc FAIL   /* invalid character detected */
410 mateuszvis 520
    cmp al, 9
430 mateuszvis 521
    jg FAIL   /* invalid character detected */
410 mateuszvis 522
    /* restore result into AX (CX contains the new digit) */
523
    xchg cx, ax
524
    /* multiply result by 10 and add cl */
525
    mul bx    /* DX AX = AX * BX(10) */
430 mateuszvis 526
    jc OVERFLOW  /* overflow */
410 mateuszvis 527
    add ax, cx
430 mateuszvis 528
    /* if CF is set then overflow occurred (overflow part lands in DX) */
410 mateuszvis 529
    jnc NEXTBYTE
530
 
430 mateuszvis 531
    OVERFLOW:
532
    xor cx, cx  /* make sure result is zeroed in case overflow occured */
533
 
410 mateuszvis 534
    FAIL:
535
    inc [err]
536
 
537
    DONE: /* save result (CX) into indirect memory address r */
538
    mov bx, [r]
539
    mov [bx], cx
540
  }
541
  return(err);
542
}
415 mateuszvis 543
 
544
 
545
/* appends a backslash if path is a directory
546
 * returns the (possibly updated) length of path */
547
unsigned short path_appendbkslash_if_dir(char *path) {
548
  unsigned short len;
549
  int attr;
550
  for (len = 0; path[len] != 0; len++);
551
  if (len == 0) return(0);
552
  if (path[len - 1] == '\\') return(len);
553
  /* */
554
  attr = file_getattr(path);
555
  if ((attr > 0) && (attr & DOS_ATTR_DIR)) {
556
    path[len++] = '\\';
557
    path[len] = 0;
558
  }
559
  return(len);
560
}
416 mateuszvis 561
 
562
 
563
/* get current path drive d (A=1, B=2, etc - 0 is "current drive")
564
 * returns 0 on success, doserr otherwise */
565
unsigned short curpathfordrv(char *buff, unsigned char d) {
566
  unsigned short r = 0;
567
 
568
  _asm {
569
    /* is d == 0? then I need to resolve current drive */
570
    cmp byte ptr [d], 0
571
    jne GETCWD
572
    /* resolve cur drive */
573
    mov ah, 0x19  /* get current default drive */
574
    int 0x21      /* al = drive (00h = A:, 01h = B:, etc) */
575
    inc al        /* convert to 1=A, 2=B, etc */
576
    mov [d], al
577
 
578
    GETCWD:
579
    /* prepend buff with drive:\ */
580
    mov si, buff
581
    mov dl, [d]
582
    mov [si], dl
583
    add byte ptr [si], 'A' - 1
584
    inc si
585
    mov [si], ':'
586
    inc si
587
    mov [si], '\\'
588
    inc si
589
 
590
    mov ah, 0x47      /* get current directory of drv DL into DS:SI */
591
    int 0x21
592
    jnc DONE
593
    mov [r], ax       /* copy result from ax */
594
 
595
    DONE:
596
  }
597
 
598
  return(r);
599
}
420 mateuszvis 600
 
601
 
2213 mateusz.vi 602
/* like strcpy() but returns the string's length */
603
unsigned short sv_strcpy(char *dst, const char *s) {
604
  unsigned short len = 0;
605
  for (;;) {
606
    *dst = *s;
607
    if (*s == 0) return(len);
608
    dst++;
609
    s++;
610
    len++;
611
  }
612
}
613
 
614
 
615
/* like sv_strcpy() but operates on far pointers */
616
unsigned short sv_strcpy_far(char far *dst, const char far *s) {
617
  unsigned short len = 0;
618
  for (;;) {
619
    *dst = *s;
620
    if (*s == 0) return(len);
621
    dst++;
622
    s++;
623
    len++;
624
  }
625
}
626
 
627
 
628
/* like strcat() */
629
void sv_strcat(char *dst, const char *s) {
630
  /* advance dst to end of string */
631
  while (*dst != 0) dst++;
632
  /* append string */
633
  sv_strcpy(dst, s);
634
}
635
 
636
 
637
/* like strcat() but operates on far pointers */
638
void sv_strcat_far(char far *dst, const char far *s) {
639
  /* advance dst to end of string */
640
  while (*dst != 0) dst++;
641
  /* append string */
642
  sv_strcpy_far(dst, s);
643
}
644
 
645
 
420 mateuszvis 646
/* fills a nls_patterns struct with current NLS patterns, returns 0 on success, DOS errcode otherwise */
647
unsigned short nls_getpatterns(struct nls_patterns *p) {
648
  unsigned short r = 0;
649
 
650
  _asm {
651
    mov ax, 0x3800  /* DOS 2+ -- Get Country Info for current country */
652
    mov dx, p       /* DS:DX points to the CountryInfoRec buffer */
653
    int 0x21
654
    jnc DONE
655
    mov [r], ax     /* copy DOS err code to r */
656
    DONE:
657
  }
658
 
659
  return(r);
660
}
661
 
662
 
663
/* computes a formatted date based on NLS patterns found in p
664
 * returns length of result */
665
unsigned short nls_format_date(char *s, unsigned short yr, unsigned char mo, unsigned char dy, const struct nls_patterns *p) {
666
  unsigned short items[3];
2220 mateusz.vi 667
  unsigned short slen;
668
 
420 mateuszvis 669
  /* preset date/month/year in proper order depending on date format */
670
  switch (p->dateformat) {
671
    case 0:  /* USA style: m d y */
672
      items[0] = mo;
673
      items[1] = dy;
674
      items[2] = yr;
675
      break;
676
    case 1:  /* EU style: d m y */
677
      items[0] = dy;
678
      items[1] = mo;
679
      items[2] = yr;
680
      break;
681
    case 2:  /* Japan-style: y m d */
682
    default:
683
      items[0] = yr;
684
      items[1] = mo;
685
      items[2] = dy;
686
      break;
687
  }
688
  /* compute the string */
2220 mateusz.vi 689
 
690
  slen = ustoa(s, items[0], 2, '0');
691
  slen += sv_strcpy(s + slen, p->datesep);
692
  slen += ustoa(s + slen, items[1], 2, '0');
693
  slen += sv_strcpy(s + slen, p->datesep);
694
  slen += ustoa(s + slen, items[2], 2, '0');
695
 
696
  return(slen);
420 mateuszvis 697
}
698
 
699
 
426 mateuszvis 700
/* computes a formatted time based on NLS patterns found in p, sc are ignored if set 0xff
420 mateuszvis 701
 * returns length of result */
426 mateuszvis 702
unsigned short nls_format_time(char *s, unsigned char ho, unsigned char mn, unsigned char sc, const struct nls_patterns *p) {
703
  char ampm = 0;
704
  unsigned short res;
705
 
420 mateuszvis 706
  if (p->timefmt == 0) {
707
    if (ho == 12) {
426 mateuszvis 708
      ampm = 'p';
420 mateuszvis 709
    } else if (ho > 12) {
710
      ho -= 12;
426 mateuszvis 711
      ampm = 'p';
420 mateuszvis 712
    } else { /* ho < 12 */
713
      if (ho == 0) ho = 12;
426 mateuszvis 714
      ampm = 'a';
420 mateuszvis 715
    }
2220 mateusz.vi 716
    res = ustoa(s, ho, 2, ' '); /* %2u */
426 mateuszvis 717
  } else {
2220 mateusz.vi 718
    res = ustoa(s, ho, 2, '0'); /* %02u */
420 mateuszvis 719
  }
426 mateuszvis 720
 
721
  /* append separator and minutes */
2220 mateusz.vi 722
  res += sv_strcpy(s + res, p->timesep);
723
  res += ustoa(s + res, mn, 2, '0'); /* %02u */
426 mateuszvis 724
 
725
  /* if seconds provided, append them, too */
2220 mateusz.vi 726
  if (sc != 0xff) {
727
    res += sv_strcpy(s + res, p->timesep);
728
    res += ustoa(s + res, sc, 2, '0'); /* %02u */
729
  }
426 mateuszvis 730
 
2220 mateusz.vi 731
  /* finally append the AM/PM char */
426 mateuszvis 732
  if (ampm != 0) s[res++] = ampm;
733
  s[res] = 0;
734
 
735
  return(res);
420 mateuszvis 736
}
737
 
738
 
739
/* computes a formatted integer number based on NLS patterns found in p
740
 * returns length of result */
423 mateuszvis 741
unsigned short nls_format_number(char *s, unsigned long num, const struct nls_patterns *p) {
742
  unsigned short sl = 0, i;
420 mateuszvis 743
  unsigned char thcount = 0;
744
 
423 mateuszvis 745
  /* write the value (reverse) with thousand separators (if any defined) */
420 mateuszvis 746
  do {
747
    if ((thcount == 3) && (p->thousep[0] != 0)) {
748
      s[sl++] = p->thousep[0];
749
      thcount = 0;
750
    }
751
    s[sl++] = '0' + num % 10;
752
    num /= 10;
753
    thcount++;
754
  } while (num > 0);
755
 
423 mateuszvis 756
  /* terminate the string */
420 mateuszvis 757
  s[sl] = 0;
758
 
423 mateuszvis 759
  /* reverse the string now (has been built in reverse) */
760
  for (i = sl / 2 + (sl & 1); i < sl; i++) {
420 mateuszvis 761
    thcount = s[i];
423 mateuszvis 762
    s[i] = s[sl - (i + 1)];   /* abc'de  if i=3 then ' <-> c */
420 mateuszvis 763
    s[sl - (i + 1)] = thcount;
764
  }
765
 
423 mateuszvis 766
  return(sl);
420 mateuszvis 767
}
437 mateuszvis 768
 
769
 
1137 mateusz.vi 770
/* capitalize an ASCIZ string following country-dependent rules */
771
void nls_strtoup(char *buff) {
772
  unsigned short errcode = 0;
773
  /* requires DOS 4+ */
774
  _asm {
775
    push ax
776
    push dx
777
 
778
    mov ax, 0x6522 /* country-dependent capitalize string (DOS 4+) */
779
    mov dx, buff   /* DS:DX -> string to capitalize */
780
    int 0x21
781
    jnc DONE
782
 
783
    mov errcode, ax /* set errcode on failure */
784
    DONE:
785
 
786
    pop dx
787
    pop ax
788
  }
789
 
2213 mateusz.vi 790
  /* do a naive upcase if DOS has no NLS support */
791
  if (errcode != 0) {
792
    while (*buff) {
793
      if (*buff < 128) { /* apply only to 7bit (low ascii) values */
794
        *buff &= 0xDF;
795
      }
796
      buff++;
797
    }
798
  }
1137 mateusz.vi 799
}
800
 
801
 
1881 mateusz.vi 802
/* reload nls ressources from svarcom.lng into svarlang_mem and rmod */
803
void nls_langreload(char *buff, unsigned short rmodseg) {
1629 mateusz.vi 804
  const char far *dosdir;
968 mateusz.vi 805
  const char far *lang;
965 mateusz.vi 806
  static unsigned short lastlang;
1629 mateusz.vi 807
  unsigned short dosdirlen;
1881 mateusz.vi 808
  unsigned short rmodenvseg = *(unsigned short far *)MK_FP(rmodseg, RMOD_OFFSET_ENVSEG);
809
  unsigned char far *rmodcritmsg = MK_FP(rmodseg, RMOD_OFFSET_CRITMSG);
810
  int i;
437 mateuszvis 811
 
812
  /* look up the LANG env variable, upcase it and copy to lang */
1881 mateusz.vi 813
  lang = env_lookup_val(rmodenvseg, "LANG");
968 mateusz.vi 814
  if ((lang == NULL) || (lang[0] == 0)) return;
2213 mateusz.vi 815
  memcpy_ltr_far(buff, lang, 2);
437 mateuszvis 816
  buff[2] = 0;
817
 
818
  /* check if there is need to reload at all */
2213 mateusz.vi 819
  if (lastlang == *((unsigned short *)buff)) return;
437 mateuszvis 820
 
968 mateusz.vi 821
  buff[4] = 0;
1881 mateusz.vi 822
  dosdir = env_lookup_val(rmodenvseg, "DOSDIR");
1629 mateusz.vi 823
  if (dosdir == NULL) return;
437 mateuszvis 824
 
2213 mateusz.vi 825
  sv_strcpy_far(buff + 4, dosdir);
826
  dosdirlen = sv_strlen(buff + 4);
1629 mateusz.vi 827
  if (buff[4 + dosdirlen - 1] == '\\') dosdirlen--;
2213 mateusz.vi 828
  memcpy_ltr(buff + 4 + dosdirlen, "\\SVARCOM.LNG", 13);
437 mateuszvis 829
 
1629 mateusz.vi 830
  /* try loading %DOSDIR%\SVARCOM.LNG */
831
  if (svarlang_load(buff + 4, buff) != 0) {
1881 mateusz.vi 832
    /* failed! try %DOSDIR%\BIN\SVARCOM.LNG */
2213 mateusz.vi 833
    memcpy_ltr(buff + 4 + dosdirlen, "\\BIN\\SVARCOM.LNG", 17);
1629 mateusz.vi 834
    if (svarlang_load(buff + 4, buff) != 0) return;
835
  }
836
 
2213 mateusz.vi 837
  memcpy_ltr_far(&lastlang, lang, 2);
1881 mateusz.vi 838
 
839
  /* update RMOD's critical handler with new strings */
2161 mateusz.vi 840
  for (i = 0; i < 9; i++) {
1881 mateusz.vi 841
    int len;
2213 mateusz.vi 842
    len = sv_strlen(svarlang_str(3, i));
1881 mateusz.vi 843
    if (len > 15) len = 15;
2213 mateusz.vi 844
    memcpy_ltr_far(rmodcritmsg + (i * 16), svarlang_str(3, i), len);
845
    memcpy_ltr_far(rmodcritmsg + (i * 16) + len, "$", 1);
1881 mateusz.vi 846
  }
847
  /* The ARIF string is special: always 4 bytes long and no $ terminator */
2213 mateusz.vi 848
  memcpy_ltr_far(rmodcritmsg + (9 * 16), svarlang_str(3,9), 4);
437 mateuszvis 849
}
571 mateuszvis 850
 
851
 
852
/* locates executable fname in path and fill res with result. returns 0 on success,
853
 * -1 on failed match and -2 on failed match + "don't even try with other paths"
854
 * extptr is filled with a ptr to the extension in fname (NULL if no extension) */
855
int lookup_cmd(char *res, const char *fname, const char *path, const char **extptr) {
856
  unsigned short lastbslash = 0;
857
  unsigned short i, len;
858
  unsigned char explicitpath = 0;
1072 mateusz.vi 859
  const char *exec_ext[] = {"COM", "EXE", "BAT", NULL};
571 mateuszvis 860
 
861
  /* does the original fname has an explicit path prefix or explicit ext? */
862
  *extptr = NULL;
863
  for (i = 0; fname[i] != 0; i++) {
864
    switch (fname[i]) {
865
      case ':':
866
      case '\\':
867
        explicitpath = 1;
868
        *extptr = NULL; /* extension is the last dot AFTER all path delimiters */
869
        break;
870
      case '.':
871
        *extptr = fname + i + 1;
872
        break;
873
    }
874
  }
875
 
1072 mateusz.vi 876
  /* if explicit ext found, make sure it is executable */
877
  if (*extptr != NULL) {
878
    for (i = 0; exec_ext[i] != NULL; i++) if (imatch(*extptr, exec_ext[i])) break;
879
    if (exec_ext[i] == NULL) return(-2); /* bad extension - don't try running it ever */
880
  }
881
 
571 mateuszvis 882
  /* normalize filename */
883
  if (file_truename(fname, res) != 0) return(-2);
884
 
885
  /* printf("truename: %s\r\n", res); */
886
 
1072 mateusz.vi 887
  /* figure out where the command starts */
571 mateuszvis 888
  for (len = 0; res[len] != 0; len++) {
889
    switch (res[len]) {
890
      case '?':   /* abort on any wildcard character */
891
      case '*':
892
        return(-2);
893
      case '\\':
894
        lastbslash = len;
895
        break;
896
    }
897
  }
898
 
899
  /* printf("lastbslash=%u\r\n", lastbslash); */
900
 
901
  /* if no path prefix was found in fname (no colon or backslash) AND we have
902
   * a path arg, then assemble path+filename */
903
  if ((!explicitpath) && (path != NULL) && (path[0] != 0)) {
2213 mateusz.vi 904
    i = sv_strlen(path);
571 mateuszvis 905
    if (path[i - 1] != '\\') i++; /* add a byte for inserting a bkslash after path */
906
    /* move the filename at the place where path will end */
2213 mateusz.vi 907
    memcpy_rtl(res + i, res + lastbslash + 1, len - lastbslash);
571 mateuszvis 908
    /* copy path in front of the filename and make sure there is a bkslash sep */
2213 mateusz.vi 909
    memcpy_ltr(res, path, i);
571 mateuszvis 910
    res[i - 1] = '\\';
911
  }
912
 
913
  /* if no extension was initially provided, try matching COM, EXE, BAT */
914
  if (*extptr == NULL) {
1072 mateusz.vi 915
    int attr;
2213 mateusz.vi 916
    len = sv_strlen(res);
1072 mateusz.vi 917
    res[len++] = '.';
918
    for (i = 0; exec_ext[i] != NULL; i++) {
2213 mateusz.vi 919
      sv_strcpy(res + len, exec_ext[i]);
571 mateuszvis 920
      /* printf("? '%s'\r\n", res); */
1072 mateusz.vi 921
      *extptr = exec_ext[i];
922
      attr = file_getattr(res);
923
      if (attr < 0) continue; /* file not found */
924
      if (attr & DOS_ATTR_DIR) continue; /* this is a directory */
925
      if (attr & DOS_ATTR_VOL) continue; /* this is a volume */
926
      return(0);
571 mateuszvis 927
    }
928
  } else { /* try finding it as-is */
929
    /* printf("? '%s'\r\n", res); */
1072 mateusz.vi 930
    int attr = file_getattr(res);
931
    if ((attr >= 0) &&  /* file exists */
932
        ((attr & DOS_ATTR_DIR) == 0) && /* is not a directory */
933
        ((attr & DOS_ATTR_VOL) == 0)) { /* is not a volume */
934
      return(0);
935
    }
571 mateuszvis 936
  }
937
 
938
  /* not found */
939
  if (explicitpath) return(-2); /* don't bother trying other paths, the caller had its own path preset anyway */
940
  return(-1);
941
}
942
 
943
 
944
/* fills fname with the path and filename to the linkfile related to the
945
 * executable link "linkname". returns 0 on success. */
946
int link_computefname(char *fname, const char *linkname, unsigned short env_seg) {
1044 mateusz.vi 947
  unsigned short pathlen, doserr = 0;
571 mateuszvis 948
 
949
  /* fetch %DOSDIR% */
950
  pathlen = env_lookup_valcopy(fname, 128, env_seg, "DOSDIR");
1988 mateusz.vi 951
  if (pathlen == 0) return(-1);
571 mateuszvis 952
 
953
  /* prep filename: %DOSDIR%\LINKS\PKG.LNK */
954
  if (fname[pathlen - 1] == '\\') pathlen--;
2220 mateusz.vi 955
  pathlen += sv_strcpy(fname + pathlen, "\\LINKS");
1044 mateusz.vi 956
  /* create \LINKS if not exists */
957
  if (file_getattr(fname) < 0) {
958
    _asm {
959
      push dx
960
      mov ah, 0x39
961
      mov dx, fname
962
      int 0x21
963
      jnc DONE
964
      mov doserr, ax
965
      DONE:
966
      pop dx
967
    }
968
    if (doserr) {
969
      output(fname);
970
      output(" - ");
971
      nls_outputnl(255, doserr);
972
      return(-1);
973
    }
974
  }
975
  /* quit early if dir does not exist (or is not a dir) */
1043 mateusz.vi 976
  if (file_getattr(fname) != DOS_ATTR_DIR) {
977
    output(fname);
978
    output(" - ");
979
    nls_outputnl(255,3); /* path not found */
980
    return(-1);
981
  }
2220 mateusz.vi 982
  sv_strcat(fname + pathlen, "\\");
983
  sv_strcat(fname + pathlen, linkname);
984
  sv_strcat(fname + pathlen, ".LNK");
571 mateuszvis 985
 
986
  return(0);
987
}
2195 mateusz.vi 988
 
989
 
990
/* like memcpy() but guarantees to copy from left to right */
991
void memcpy_ltr(void *d, const void *s, unsigned short len) {
992
  unsigned char const *ss = s;
993
  unsigned char *dd = d;
994
 
995
  while (len--) {
996
    *dd = *ss;
997
    ss++;
998
    dd++;
999
  }
1000
}
1001
 
2213 mateusz.vi 1002
 
1003
/* like memcpy_ltr() but operates on far pointers */
1004
void memcpy_ltr_far(void far *d, const void far *s, unsigned short len) {
1005
  unsigned char const far *ss = s;
1006
  unsigned char far *dd = d;
1007
 
1008
  while (len--) {
1009
    *dd = *ss;
1010
    ss++;
1011
    dd++;
1012
  }
1013
}
1014
 
1015
 
2195 mateusz.vi 1016
/* like memcpy() but guarantees to copy from right to left */
1017
void memcpy_rtl(void *d, const void *s, unsigned short len) {
1018
  unsigned char const *ss = s;
1019
  unsigned char *dd = d;
1020
 
1021
  dd += len - 1;
1022
  ss += len - 1;
1023
  while (len--) {
1024
    *dd = *ss;
1025
    ss--;
1026
    dd--;
1027
  }
1028
}
2213 mateusz.vi 1029
 
1030
 
1031
/* like bzero(), but accepts far pointers */
1032
void sv_bzero(void far *dst, unsigned short len) {
1033
  char far *d = dst;
1034
  while (len--) {
1035
    *d = 0;
1036
    d++;
1037
  }
1038
}
2218 mateusz.vi 1039
 
1040
 
1041
/* like memset() */
1042
void sv_memset(void *dst, unsigned char c, unsigned short len) {
1043
  unsigned char *d = dst;
1044
  while (len--) {
1045
    *d = c;
1046
    d++;
1047
  }
1048
}
2222 mateusz.vi 1049
 
1050
 
1051
/* replaces characters a by b in s */
1052
void sv_strtr(char *s, char a, char b) {
1053
  while (*s) {
1054
    if (*s == a) *s = b;
1055
    s++;
1056
  }
1057
}
1058
 
1059
 
1060
/* inserts string s2 into s1 in place of the first % character */
1061
void sv_insert_str_in_str(char *s1, const char *s2) {
1062
  unsigned short s2len;
1063
 
1064
  /* fast forward s1 to either % or 0 */
1065
  while ((*s1 != 0) && (*s1 != '%')) s1++;
1066
 
1067
  /* if not % then quit */
1068
  if (*s1 != '%') return;
1069
 
1070
  /* make room for s2, unless s2 is exactly 1 byte long */
1071
  s2len = sv_strlen(s2);
1072
  if (s2len == 0) {
1073
    memcpy_ltr(s1, s1 + 1, sv_strlen(s1 + 1) + 1);
1074
  } else if (s2len > 1) {
1075
    memcpy_rtl(s1 + s2len, s1 + 1, sv_strlen(s1 + 1) + 1);
1076
  }
1077
 
1078
  /* write s2 to the cleared space */
1079
  if (s2len != 0) memcpy_ltr(s1, s2, s2len);
1080
}