Subversion Repositories SvarDOS

Rev

Rev 426 | Rev 437 | 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
 *
4
 * Copyright (C) 2021 Mateusz Viste
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
 * Copyright (C) 2021 Mateusz Viste
28
 */
29
 
396 mateuszvis 30
#include <i86.h>    /* MK_FP() */
420 mateuszvis 31
#include <stdio.h>  /* sprintf() */
396 mateuszvis 32
 
352 mateuszvis 33
#include "helpers.h"
34
 
420 mateuszvis 35
 
352 mateuszvis 36
/* case-insensitive comparison of strings, returns non-zero on equality */
37
int imatch(const char *s1, const char *s2) {
38
  for (;;) {
39
    char c1, c2;
40
    c1 = *s1;
41
    c2 = *s2;
42
    if ((c1 >= 'a') && (c1 <= 'z')) c1 -= ('a' - 'A');
43
    if ((c2 >= 'a') && (c2 <= 'z')) c2 -= ('a' - 'A');
44
    /* */
45
    if (c1 != c2) return(0);
46
    if (c1 == 0) return(1);
47
    s1++;
48
    s2++;
49
  }
50
}
51
 
52
 
53
/* returns zero if s1 starts with s2 */
54
int strstartswith(const char *s1, const char *s2) {
55
  while (*s2 != 0) {
56
    if (*s1 != *s2) return(-1);
57
    s1++;
58
    s2++;
59
  }
60
  return(0);
61
}
369 mateuszvis 62
 
63
 
64
/* outputs a NULL-terminated string to stdout */
65
void output_internal(const char *s, unsigned short nl) {
66
  _asm {
67
    mov ah, 0x02 /* AH=9 - write character in DL to stdout */
68
    mov si, s
69
    cld          /* clear DF so lodsb increments SI */
70
    NEXTBYTE:
71
    lodsb /* load byte from DS:SI into AL, SI++ */
72
    mov dl, al
73
    or al, 0  /* is al == 0? */
74
    jz DONE
75
    int 0x21
76
    jmp NEXTBYTE
77
    DONE:
78
    or nl, 0
79
    jz FINITO
80
    /* print out a CR/LF trailer if nl set */
81
    mov dl, 0x0D /* CR */
82
    int 0x21
83
    mov dl, 0x0A /* LF */
84
    int 0x21
85
    FINITO:
86
  }
87
}
388 mateuszvis 88
 
89
 
90
/* find first matching files using a FindFirst DOS call
91
 * returns 0 on success or a DOS err code on failure */
92
unsigned short findfirst(struct DTA *dta, const char *pattern, unsigned short attr) {
93
  unsigned short res = 0;
94
  _asm {
95
    /* set DTA location */
96
    mov ah, 0x1a
97
    mov dx, dta
98
    int 0x21
99
    /* */
100
    mov ah, 0x4e    /* FindFirst */
101
    mov dx, pattern
102
    mov cx, attr
103
    int 0x21        /* CF set on error + err code in AX, DTA filled with FileInfoRec on success */
104
    jnc DONE
105
    mov [res], ax
106
    DONE:
107
  }
108
  return(res);
109
}
110
 
111
 
112
/* find next matching, ie. continues an action intiated by findfirst() */
113
unsigned short findnext(struct DTA *dta) {
114
  unsigned short res = 0;
115
  _asm {
116
    mov ah, 0x4f    /* FindNext */
117
    mov dx, dta
118
    int 0x21        /* CF set on error + err code in AX, DTA filled with FileInfoRec on success */
119
    jnc DONE
120
    mov [res], ax
121
    DONE:
122
  }
123
  return(res);
124
}
392 mateuszvis 125
 
126
 
127
/* print s string and wait for a single key press from stdin. accepts only
128
 * key presses defined in the c ASCIIZ string. returns offset of pressed key
129
 * in string. keys in c MUST BE UPPERCASE! */
130
unsigned short askchoice(const char *s, const char *c) {
131
  unsigned short res;
132
  char key = 0;
133
 
134
  AGAIN:
135
  output(s);
136
  output(" ");
137
 
138
  _asm {
139
    push ax
140
    push dx
141
 
142
    mov ax, 0x0c01 /* clear input buffer and execute getchar (INT 21h,AH=1) */
143
    int 0x21
144
    /* if AL == 0 then this is an extended character */
145
    test al, al
146
    jnz GOTCHAR
147
    mov ah, 0x08   /* read again to flush extended char from input buffer */
148
    int 0x21
149
    xor al, al     /* all extended chars are ignored */
150
    GOTCHAR:       /* received key is in AL now */
151
    mov [key], al  /* save key */
152
 
153
    /* print a cr/lf */
154
    mov ah, 0x02
155
    mov dl, 0x0D
156
    int 0x21
157
    mov dl, 0x0A
158
    int 0x21
159
 
160
    pop dx
161
    pop ax
162
  }
163
 
164
  /* ucase() result */
165
  if ((key >= 'a') && (key <= 'z')) key -= ('a' - 'A');
166
 
167
  /* is there a match? */
168
  for (res = 0; c[res] != 0; res++) if (c[res] == key) return(res);
169
 
170
  goto AGAIN;
171
}
172
 
173
 
399 mateuszvis 174
/* converts a path to its canonic representation, returns 0 on success
175
 * or DOS err on failure (invalid drive) */
176
unsigned short file_truename(const char *src, char *dst) {
177
  unsigned short res = 0;
392 mateuszvis 178
  _asm {
399 mateuszvis 179
    push es
392 mateuszvis 180
    mov ah, 0x60  /* query truename, DS:SI=src, ES:DI=dst */
181
    push ds
182
    pop es
183
    mov si, src
184
    mov di, dst
185
    int 0x21
399 mateuszvis 186
    jnc DONE
187
    mov [res], ax
188
    DONE:
189
    pop es
392 mateuszvis 190
  }
399 mateuszvis 191
  return(res);
392 mateuszvis 192
}
193
 
194
 
195
/* returns DOS attributes of file, or -1 on error */
196
int file_getattr(const char *fname) {
197
  int res = -1;
198
  _asm {
199
    mov ax, 0x4300  /* query file attributes, fname at DS:DX */
200
    mov dx, fname
201
    int 0x21        /* CX=attributes if CF=0, otherwise AX=errno */
202
    jc DONE
203
    mov [res], cx
204
    DONE:
205
  }
206
  return(res);
207
}
396 mateuszvis 208
 
209
 
210
/* returns screen's width (in columns) */
211
unsigned short screen_getwidth(void) {
212
  /* BIOS 0040:004A = word containing screen width in text columns */
213
  unsigned short far *scrw = MK_FP(0x40, 0x4a);
214
  return(*scrw);
215
}
216
 
217
 
218
/* returns screen's height (in rows) */
219
unsigned short screen_getheight(void) {
220
  /* BIOS 0040:0084 = byte containing maximum valid row value (EGA ONLY) */
221
  unsigned char far *scrh = MK_FP(0x40, 0x84);
222
  if (*scrh == 0) return(25);  /* pre-EGA adapter */
223
  return(*scrh + 1);
224
}
225
 
226
 
227
/* displays the "Press any key to continue" msg and waits for a keypress */
228
void press_any_key(void) {
229
  output("Press any key to continue...");
230
  _asm {
231
    mov ah, 0x08  /* no echo console input */
232
    int 0x21      /* pressed key in AL now (0 for extended keys) */
233
    test al, al
234
    jnz DONE
235
    int 0x21      /* executed ah=8 again to read the rest of extended key */
236
    DONE:
237
    /* output CR/LF */
238
    mov ah, 0x02
239
    mov dl, 0x0D
240
    int 0x21
241
    mov dl, 0x0A
242
    int 0x21
243
  }
244
}
399 mateuszvis 245
 
246
 
247
/* validate a drive (A=0, B=1, etc). returns 1 if valid, 0 otherwise */
248
int isdrivevalid(unsigned char drv) {
249
  _asm {
250
    mov ah, 0x19  /* query default (current) disk */
251
    int 0x21      /* drive in AL (0=A, 1=B, etc) */
252
    mov ch, al    /* save current drive to ch */
253
    /* try setting up the drive as current */
254
    mov ah, 0x0E   /* select default drive */
255
    mov dl, [drv]  /* 0=A, 1=B, etc */
256
    int 0x21
257
    /* this call does not set CF on error, I must check cur drive to look for success */
258
    mov ah, 0x19  /* query default (current) disk */
259
    int 0x21      /* drive in AL (0=A, 1=B, etc) */
260
    mov [drv], 1  /* preset result as success */
261
    cmp al, dl    /* is eq? */
262
    je DONE
263
    mov [drv], 0  /* fail */
264
    jmp FAILED
265
    DONE:
266
    /* set current drive back to what it was initially */
267
    mov ah, 0x0E
268
    mov dl, ch
269
    int 0x21
270
    FAILED:
271
  }
272
  return(drv);
273
}
406 mateuszvis 274
 
275
 
276
/* converts a 8+3 filename into 11-bytes FCB format (MYFILE  EXT) */
277
void file_fname2fcb(char *dst, const char *src) {
278
  unsigned short i;
279
 
280
  /* fill dst with 11 spaces and a NULL terminator */
420 mateuszvis 281
  for (i = 0; i < 11; i++) dst[i] = ' ';
282
  dst[11] = 0;
406 mateuszvis 283
 
284
  /* copy fname until dot (.) or 8 characters */
285
  for (i = 0; i < 8; i++) {
286
    if ((src[i] == '.') || (src[i] == 0)) break;
287
    dst[i] = src[i];
288
  }
289
 
290
  /* advance src until extension or end of string */
291
  src += i;
292
  for (;;) {
293
    if (*src == '.') {
294
      src++; /* next character is extension */
295
      break;
296
    }
297
    if (*src == 0) break;
298
  }
299
 
300
  /* copy extension to dst (3 chars max) */
301
  dst += 8;
302
  for (i = 0; i < 3; i++) {
303
    if (src[i] == 0) break;
304
    dst[i] = src[i];
305
  }
306
}
307
 
308
 
309
/* converts a 11-bytes FCB filename (MYFILE  EXT) into 8+3 format (MYFILE.EXT) */
310
void file_fcb2fname(char *dst, const char *src) {
311
  unsigned short i, end = 0;
312
 
313
  for (i = 0; i < 8; i++) {
314
    dst[i] = src[i];
315
    if (dst[i] != ' ') end = i + 1;
316
  }
317
 
318
  /* is there an extension? */
319
  if (src[8] == ' ') {
320
    dst[end] = 0;
321
  } else { /* found extension: copy it until first space */
322
    dst[end++] = '.';
323
    for (i = 8; i < 11; i++) {
324
      if (src[i] == ' ') break;
325
      dst[end++] = src[i];
326
    }
327
    dst[end] = 0;
328
  }
329
}
410 mateuszvis 330
 
331
 
430 mateuszvis 332
/* converts an ASCIIZ string into an unsigned short. returns 0 on success.
333
 * on error, result will contain all valid digits that were read until
334
 * error occurred (0 on overflow or if parsing failed immediately) */
426 mateuszvis 335
int atous(unsigned short *r, const char *s) {
410 mateuszvis 336
  int err = 0;
337
 
338
  _asm {
339
    mov si, s
340
    xor ax, ax  /* general purpose register */
341
    xor cx, cx  /* contains the result */
342
    mov bx, 10  /* used as a multiplicative step */
343
 
344
    NEXTBYTE:
345
    xchg cx, ax /* move result into cx temporarily */
346
    lodsb  /* AL = DS:[SI++] */
347
    /* is AL 0? if so we're done */
348
    test al, al
349
    jz DONE
350
    /* validate that AL is in range '0'-'9' */
351
    sub al, '0'
430 mateuszvis 352
    jc FAIL   /* invalid character detected */
410 mateuszvis 353
    cmp al, 9
430 mateuszvis 354
    jg FAIL   /* invalid character detected */
410 mateuszvis 355
    /* restore result into AX (CX contains the new digit) */
356
    xchg cx, ax
357
    /* multiply result by 10 and add cl */
358
    mul bx    /* DX AX = AX * BX(10) */
430 mateuszvis 359
    jc OVERFLOW  /* overflow */
410 mateuszvis 360
    add ax, cx
430 mateuszvis 361
    /* if CF is set then overflow occurred (overflow part lands in DX) */
410 mateuszvis 362
    jnc NEXTBYTE
363
 
430 mateuszvis 364
    OVERFLOW:
365
    xor cx, cx  /* make sure result is zeroed in case overflow occured */
366
 
410 mateuszvis 367
    FAIL:
368
    inc [err]
369
 
370
    DONE: /* save result (CX) into indirect memory address r */
371
    mov bx, [r]
372
    mov [bx], cx
373
  }
374
  return(err);
375
}
415 mateuszvis 376
 
377
 
378
/* appends a backslash if path is a directory
379
 * returns the (possibly updated) length of path */
380
unsigned short path_appendbkslash_if_dir(char *path) {
381
  unsigned short len;
382
  int attr;
383
  for (len = 0; path[len] != 0; len++);
384
  if (len == 0) return(0);
385
  if (path[len - 1] == '\\') return(len);
386
  /* */
387
  attr = file_getattr(path);
388
  if ((attr > 0) && (attr & DOS_ATTR_DIR)) {
389
    path[len++] = '\\';
390
    path[len] = 0;
391
  }
392
  return(len);
393
}
416 mateuszvis 394
 
395
 
396
/* get current path drive d (A=1, B=2, etc - 0 is "current drive")
397
 * returns 0 on success, doserr otherwise */
398
unsigned short curpathfordrv(char *buff, unsigned char d) {
399
  unsigned short r = 0;
400
 
401
  _asm {
402
    /* is d == 0? then I need to resolve current drive */
403
    cmp byte ptr [d], 0
404
    jne GETCWD
405
    /* resolve cur drive */
406
    mov ah, 0x19  /* get current default drive */
407
    int 0x21      /* al = drive (00h = A:, 01h = B:, etc) */
408
    inc al        /* convert to 1=A, 2=B, etc */
409
    mov [d], al
410
 
411
    GETCWD:
412
    /* prepend buff with drive:\ */
413
    mov si, buff
414
    mov dl, [d]
415
    mov [si], dl
416
    add byte ptr [si], 'A' - 1
417
    inc si
418
    mov [si], ':'
419
    inc si
420
    mov [si], '\\'
421
    inc si
422
 
423
    mov ah, 0x47      /* get current directory of drv DL into DS:SI */
424
    int 0x21
425
    jnc DONE
426
    mov [r], ax       /* copy result from ax */
427
 
428
    DONE:
429
  }
430
 
431
  return(r);
432
}
420 mateuszvis 433
 
434
 
435
/* fills a nls_patterns struct with current NLS patterns, returns 0 on success, DOS errcode otherwise */
436
unsigned short nls_getpatterns(struct nls_patterns *p) {
437
  unsigned short r = 0;
438
 
439
  _asm {
440
    mov ax, 0x3800  /* DOS 2+ -- Get Country Info for current country */
441
    mov dx, p       /* DS:DX points to the CountryInfoRec buffer */
442
    int 0x21
443
    jnc DONE
444
    mov [r], ax     /* copy DOS err code to r */
445
    DONE:
446
  }
447
 
448
  return(r);
449
}
450
 
451
 
452
/* computes a formatted date based on NLS patterns found in p
453
 * returns length of result */
454
unsigned short nls_format_date(char *s, unsigned short yr, unsigned char mo, unsigned char dy, const struct nls_patterns *p) {
455
  unsigned short items[3];
456
  /* preset date/month/year in proper order depending on date format */
457
  switch (p->dateformat) {
458
    case 0:  /* USA style: m d y */
459
      items[0] = mo;
460
      items[1] = dy;
461
      items[2] = yr;
462
      break;
463
    case 1:  /* EU style: d m y */
464
      items[0] = dy;
465
      items[1] = mo;
466
      items[2] = yr;
467
      break;
468
    case 2:  /* Japan-style: y m d */
469
    default:
470
      items[0] = yr;
471
      items[1] = mo;
472
      items[2] = dy;
473
      break;
474
  }
475
  /* compute the string */
476
  return(sprintf(s, "%02u%s%02u%s%02u", items[0], p->datesep, items[1], p->datesep, items[2]));
477
}
478
 
479
 
426 mateuszvis 480
/* computes a formatted time based on NLS patterns found in p, sc are ignored if set 0xff
420 mateuszvis 481
 * returns length of result */
426 mateuszvis 482
unsigned short nls_format_time(char *s, unsigned char ho, unsigned char mn, unsigned char sc, const struct nls_patterns *p) {
483
  char ampm = 0;
484
  unsigned short res;
485
 
420 mateuszvis 486
  if (p->timefmt == 0) {
487
    if (ho == 12) {
426 mateuszvis 488
      ampm = 'p';
420 mateuszvis 489
    } else if (ho > 12) {
490
      ho -= 12;
426 mateuszvis 491
      ampm = 'p';
420 mateuszvis 492
    } else { /* ho < 12 */
493
      if (ho == 0) ho = 12;
426 mateuszvis 494
      ampm = 'a';
420 mateuszvis 495
    }
426 mateuszvis 496
    res = sprintf(s, "%2u", ho);
497
  } else {
498
    res = sprintf(s, "%02u", ho);
420 mateuszvis 499
  }
426 mateuszvis 500
 
501
  /* append separator and minutes */
502
  res += sprintf(s + res, "%s%02u", p->timesep, mn);
503
 
504
  /* if seconds provided, append them, too */
505
  if (sc != 0xff) res += sprintf(s + res, "%s%02u", p->timesep, sc);
506
 
507
  /* finally append AM/PM char */
508
  if (ampm != 0) s[res++] = ampm;
509
  s[res] = 0;
510
 
511
  return(res);
420 mateuszvis 512
}
513
 
514
 
515
/* computes a formatted integer number based on NLS patterns found in p
516
 * returns length of result */
423 mateuszvis 517
unsigned short nls_format_number(char *s, unsigned long num, const struct nls_patterns *p) {
518
  unsigned short sl = 0, i;
420 mateuszvis 519
  unsigned char thcount = 0;
520
 
423 mateuszvis 521
  /* write the value (reverse) with thousand separators (if any defined) */
420 mateuszvis 522
  do {
523
    if ((thcount == 3) && (p->thousep[0] != 0)) {
524
      s[sl++] = p->thousep[0];
525
      thcount = 0;
526
    }
527
    s[sl++] = '0' + num % 10;
528
    num /= 10;
529
    thcount++;
530
  } while (num > 0);
531
 
423 mateuszvis 532
  /* terminate the string */
420 mateuszvis 533
  s[sl] = 0;
534
 
423 mateuszvis 535
  /* reverse the string now (has been built in reverse) */
536
  for (i = sl / 2 + (sl & 1); i < sl; i++) {
420 mateuszvis 537
    thcount = s[i];
423 mateuszvis 538
    s[i] = s[sl - (i + 1)];   /* abc'de  if i=3 then ' <-> c */
420 mateuszvis 539
    s[sl - (i + 1)] = thcount;
540
  }
541
 
423 mateuszvis 542
  return(sl);
420 mateuszvis 543
}