Subversion Repositories SvarDOS

Rev

Rev 445 | Rev 538 | 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() */
437 mateuszvis 32
#include <string.h> /* memcpy() */
396 mateuszvis 33
 
437 mateuszvis 34
#include "deflang.h"
35
 
36
#include "env.h"
37
 
352 mateuszvis 38
#include "helpers.h"
39
 
420 mateuszvis 40
 
530 mateuszvis 41
/* case-insensitive comparison of strings, compares up to maxlen characters.
42
 * returns non-zero on equality. */
43
int imatchlim(const char *s1, const char *s2, unsigned short maxlen) {
44
  while (maxlen--) {
352 mateuszvis 45
    char c1, c2;
46
    c1 = *s1;
47
    c2 = *s2;
48
    if ((c1 >= 'a') && (c1 <= 'z')) c1 -= ('a' - 'A');
49
    if ((c2 >= 'a') && (c2 <= 'z')) c2 -= ('a' - 'A');
50
    /* */
51
    if (c1 != c2) return(0);
530 mateuszvis 52
    if (c1 == 0) break;
352 mateuszvis 53
    s1++;
54
    s2++;
55
  }
530 mateuszvis 56
  return(1);
352 mateuszvis 57
}
58
 
59
 
60
/* returns zero if s1 starts with s2 */
61
int strstartswith(const char *s1, const char *s2) {
62
  while (*s2 != 0) {
63
    if (*s1 != *s2) return(-1);
64
    s1++;
65
    s2++;
66
  }
67
  return(0);
68
}
369 mateuszvis 69
 
70
 
71
/* outputs a NULL-terminated string to stdout */
445 mateuszvis 72
void output_internal(const char *s, unsigned char nl) {
73
  const static unsigned char *crlf = "\r\n$";
369 mateuszvis 74
  _asm {
445 mateuszvis 75
    push ds
76
    pop es         /* make sure es=ds (scasb uses es) */
77
    /* get length of s into CX */
78
    mov ax, 0x4000 /* ah=DOS "write to file" and AL=0 for NULL matching */
79
    mov dx, s      /* set dx to string (required for later) */
80
    mov di, dx     /* set di to string (for NULL matching) */
81
    mov cx, 0xffff /* preset cx to 65535 (-1) */
82
    cld            /* clear DF so scasb increments DI */
83
    repne scasb    /* cmp al, es:[di], inc di, dec cx until match found */
84
    /* CX contains (65535 - strlen(s)) now */
85
    not cx         /* reverse all bits so I get (strlen(s) + 1) */
86
    dec cx         /* this is CX length */
87
    jz WRITEDONE   /* do nothing for empty strings */
88
 
89
    /* output by writing to stdout */
90
    /* mov ah, 0x40 */  /* DOS 2+ -- write to file via handle */
91
    mov bx, 1      /* handle 1 is always stdout */
92
    /* mov cx, xxx */ /* write CX bytes */
93
    /* mov dx, s   */ /* DS:DX is the source of bytes to "write" */
369 mateuszvis 94
    int 0x21
445 mateuszvis 95
    WRITEDONE:
96
 
97
    /* print out a CR/LF trailer if nl set */
98
    or byte ptr [nl], 0
369 mateuszvis 99
    jz FINITO
445 mateuszvis 100
    mov ah, 0x09
101
    mov dx, crlf
369 mateuszvis 102
    int 0x21
103
    FINITO:
104
  }
105
}
388 mateuszvis 106
 
107
 
445 mateuszvis 108
void nls_output_internal(unsigned short id, unsigned char nl) {
437 mateuszvis 109
  const char *ptr = langblock + 4; /* first 4 bytes are lang id and lang len */
110
  const char *NOTFOUND = "NLS_STRING_NOT_FOUND";
111
  /* find the string id in langblock memory */
112
  for (;;) {
113
    if (((unsigned short *)ptr)[0] == id) {
114
      ptr += 3;
115
      break;
116
    }
117
    if (ptr[2] == 0) {
118
      ptr = NOTFOUND;
119
      break;
120
    }
121
    ptr += ptr[2] + 3;
122
  }
123
  output_internal(ptr, nl);
124
}
125
 
126
 
388 mateuszvis 127
/* find first matching files using a FindFirst DOS call
128
 * returns 0 on success or a DOS err code on failure */
129
unsigned short findfirst(struct DTA *dta, const char *pattern, unsigned short attr) {
130
  unsigned short res = 0;
131
  _asm {
132
    /* set DTA location */
133
    mov ah, 0x1a
134
    mov dx, dta
135
    int 0x21
136
    /* */
137
    mov ah, 0x4e    /* FindFirst */
138
    mov dx, pattern
139
    mov cx, attr
140
    int 0x21        /* CF set on error + err code in AX, DTA filled with FileInfoRec on success */
141
    jnc DONE
142
    mov [res], ax
143
    DONE:
144
  }
145
  return(res);
146
}
147
 
148
 
149
/* find next matching, ie. continues an action intiated by findfirst() */
150
unsigned short findnext(struct DTA *dta) {
151
  unsigned short res = 0;
152
  _asm {
153
    mov ah, 0x4f    /* FindNext */
154
    mov dx, dta
155
    int 0x21        /* CF set on error + err code in AX, DTA filled with FileInfoRec on success */
156
    jnc DONE
157
    mov [res], ax
158
    DONE:
159
  }
160
  return(res);
161
}
392 mateuszvis 162
 
163
 
164
/* print s string and wait for a single key press from stdin. accepts only
165
 * key presses defined in the c ASCIIZ string. returns offset of pressed key
166
 * in string. keys in c MUST BE UPPERCASE! */
167
unsigned short askchoice(const char *s, const char *c) {
168
  unsigned short res;
169
  char key = 0;
170
 
171
  AGAIN:
172
  output(s);
173
  output(" ");
174
 
175
  _asm {
176
    push ax
177
    push dx
178
 
179
    mov ax, 0x0c01 /* clear input buffer and execute getchar (INT 21h,AH=1) */
180
    int 0x21
181
    /* if AL == 0 then this is an extended character */
182
    test al, al
183
    jnz GOTCHAR
184
    mov ah, 0x08   /* read again to flush extended char from input buffer */
185
    int 0x21
186
    xor al, al     /* all extended chars are ignored */
187
    GOTCHAR:       /* received key is in AL now */
188
    mov [key], al  /* save key */
189
 
190
    /* print a cr/lf */
191
    mov ah, 0x02
192
    mov dl, 0x0D
193
    int 0x21
194
    mov dl, 0x0A
195
    int 0x21
196
 
197
    pop dx
198
    pop ax
199
  }
200
 
201
  /* ucase() result */
202
  if ((key >= 'a') && (key <= 'z')) key -= ('a' - 'A');
203
 
204
  /* is there a match? */
205
  for (res = 0; c[res] != 0; res++) if (c[res] == key) return(res);
206
 
207
  goto AGAIN;
208
}
209
 
210
 
399 mateuszvis 211
/* converts a path to its canonic representation, returns 0 on success
212
 * or DOS err on failure (invalid drive) */
213
unsigned short file_truename(const char *src, char *dst) {
214
  unsigned short res = 0;
392 mateuszvis 215
  _asm {
399 mateuszvis 216
    push es
392 mateuszvis 217
    mov ah, 0x60  /* query truename, DS:SI=src, ES:DI=dst */
218
    push ds
219
    pop es
220
    mov si, src
221
    mov di, dst
222
    int 0x21
399 mateuszvis 223
    jnc DONE
224
    mov [res], ax
225
    DONE:
226
    pop es
392 mateuszvis 227
  }
399 mateuszvis 228
  return(res);
392 mateuszvis 229
}
230
 
231
 
232
/* returns DOS attributes of file, or -1 on error */
233
int file_getattr(const char *fname) {
234
  int res = -1;
235
  _asm {
236
    mov ax, 0x4300  /* query file attributes, fname at DS:DX */
237
    mov dx, fname
238
    int 0x21        /* CX=attributes if CF=0, otherwise AX=errno */
239
    jc DONE
240
    mov [res], cx
241
    DONE:
242
  }
243
  return(res);
244
}
396 mateuszvis 245
 
246
 
247
/* returns screen's width (in columns) */
248
unsigned short screen_getwidth(void) {
249
  /* BIOS 0040:004A = word containing screen width in text columns */
250
  unsigned short far *scrw = MK_FP(0x40, 0x4a);
251
  return(*scrw);
252
}
253
 
254
 
255
/* returns screen's height (in rows) */
256
unsigned short screen_getheight(void) {
257
  /* BIOS 0040:0084 = byte containing maximum valid row value (EGA ONLY) */
258
  unsigned char far *scrh = MK_FP(0x40, 0x84);
259
  if (*scrh == 0) return(25);  /* pre-EGA adapter */
260
  return(*scrh + 1);
261
}
262
 
263
 
264
/* displays the "Press any key to continue" msg and waits for a keypress */
265
void press_any_key(void) {
437 mateuszvis 266
  nls_output(15, 1); /* Press any key to continue... */
396 mateuszvis 267
  _asm {
268
    mov ah, 0x08  /* no echo console input */
269
    int 0x21      /* pressed key in AL now (0 for extended keys) */
270
    test al, al
271
    jnz DONE
272
    int 0x21      /* executed ah=8 again to read the rest of extended key */
273
    DONE:
274
    /* output CR/LF */
275
    mov ah, 0x02
276
    mov dl, 0x0D
277
    int 0x21
278
    mov dl, 0x0A
279
    int 0x21
280
  }
281
}
399 mateuszvis 282
 
283
 
284
/* validate a drive (A=0, B=1, etc). returns 1 if valid, 0 otherwise */
285
int isdrivevalid(unsigned char drv) {
286
  _asm {
287
    mov ah, 0x19  /* query default (current) disk */
288
    int 0x21      /* drive in AL (0=A, 1=B, etc) */
289
    mov ch, al    /* save current drive to ch */
290
    /* try setting up the drive as current */
291
    mov ah, 0x0E   /* select default drive */
292
    mov dl, [drv]  /* 0=A, 1=B, etc */
293
    int 0x21
294
    /* this call does not set CF on error, I must check cur drive to look for success */
295
    mov ah, 0x19  /* query default (current) disk */
296
    int 0x21      /* drive in AL (0=A, 1=B, etc) */
297
    mov [drv], 1  /* preset result as success */
298
    cmp al, dl    /* is eq? */
299
    je DONE
300
    mov [drv], 0  /* fail */
301
    jmp FAILED
302
    DONE:
303
    /* set current drive back to what it was initially */
304
    mov ah, 0x0E
305
    mov dl, ch
306
    int 0x21
307
    FAILED:
308
  }
309
  return(drv);
310
}
406 mateuszvis 311
 
312
 
313
/* converts a 8+3 filename into 11-bytes FCB format (MYFILE  EXT) */
314
void file_fname2fcb(char *dst, const char *src) {
315
  unsigned short i;
316
 
317
  /* fill dst with 11 spaces and a NULL terminator */
420 mateuszvis 318
  for (i = 0; i < 11; i++) dst[i] = ' ';
319
  dst[11] = 0;
406 mateuszvis 320
 
321
  /* copy fname until dot (.) or 8 characters */
322
  for (i = 0; i < 8; i++) {
323
    if ((src[i] == '.') || (src[i] == 0)) break;
324
    dst[i] = src[i];
325
  }
326
 
327
  /* advance src until extension or end of string */
328
  src += i;
329
  for (;;) {
330
    if (*src == '.') {
331
      src++; /* next character is extension */
332
      break;
333
    }
334
    if (*src == 0) break;
335
  }
336
 
337
  /* copy extension to dst (3 chars max) */
338
  dst += 8;
339
  for (i = 0; i < 3; i++) {
340
    if (src[i] == 0) break;
341
    dst[i] = src[i];
342
  }
343
}
344
 
345
 
346
/* converts a 11-bytes FCB filename (MYFILE  EXT) into 8+3 format (MYFILE.EXT) */
347
void file_fcb2fname(char *dst, const char *src) {
348
  unsigned short i, end = 0;
349
 
350
  for (i = 0; i < 8; i++) {
351
    dst[i] = src[i];
352
    if (dst[i] != ' ') end = i + 1;
353
  }
354
 
355
  /* is there an extension? */
356
  if (src[8] == ' ') {
357
    dst[end] = 0;
358
  } else { /* found extension: copy it until first space */
359
    dst[end++] = '.';
360
    for (i = 8; i < 11; i++) {
361
      if (src[i] == ' ') break;
362
      dst[end++] = src[i];
363
    }
364
    dst[end] = 0;
365
  }
366
}
410 mateuszvis 367
 
368
 
430 mateuszvis 369
/* converts an ASCIIZ string into an unsigned short. returns 0 on success.
370
 * on error, result will contain all valid digits that were read until
371
 * error occurred (0 on overflow or if parsing failed immediately) */
426 mateuszvis 372
int atous(unsigned short *r, const char *s) {
410 mateuszvis 373
  int err = 0;
374
 
375
  _asm {
376
    mov si, s
377
    xor ax, ax  /* general purpose register */
378
    xor cx, cx  /* contains the result */
379
    mov bx, 10  /* used as a multiplicative step */
380
 
381
    NEXTBYTE:
382
    xchg cx, ax /* move result into cx temporarily */
383
    lodsb  /* AL = DS:[SI++] */
384
    /* is AL 0? if so we're done */
385
    test al, al
386
    jz DONE
387
    /* validate that AL is in range '0'-'9' */
388
    sub al, '0'
430 mateuszvis 389
    jc FAIL   /* invalid character detected */
410 mateuszvis 390
    cmp al, 9
430 mateuszvis 391
    jg FAIL   /* invalid character detected */
410 mateuszvis 392
    /* restore result into AX (CX contains the new digit) */
393
    xchg cx, ax
394
    /* multiply result by 10 and add cl */
395
    mul bx    /* DX AX = AX * BX(10) */
430 mateuszvis 396
    jc OVERFLOW  /* overflow */
410 mateuszvis 397
    add ax, cx
430 mateuszvis 398
    /* if CF is set then overflow occurred (overflow part lands in DX) */
410 mateuszvis 399
    jnc NEXTBYTE
400
 
430 mateuszvis 401
    OVERFLOW:
402
    xor cx, cx  /* make sure result is zeroed in case overflow occured */
403
 
410 mateuszvis 404
    FAIL:
405
    inc [err]
406
 
407
    DONE: /* save result (CX) into indirect memory address r */
408
    mov bx, [r]
409
    mov [bx], cx
410
  }
411
  return(err);
412
}
415 mateuszvis 413
 
414
 
415
/* appends a backslash if path is a directory
416
 * returns the (possibly updated) length of path */
417
unsigned short path_appendbkslash_if_dir(char *path) {
418
  unsigned short len;
419
  int attr;
420
  for (len = 0; path[len] != 0; len++);
421
  if (len == 0) return(0);
422
  if (path[len - 1] == '\\') return(len);
423
  /* */
424
  attr = file_getattr(path);
425
  if ((attr > 0) && (attr & DOS_ATTR_DIR)) {
426
    path[len++] = '\\';
427
    path[len] = 0;
428
  }
429
  return(len);
430
}
416 mateuszvis 431
 
432
 
433
/* get current path drive d (A=1, B=2, etc - 0 is "current drive")
434
 * returns 0 on success, doserr otherwise */
435
unsigned short curpathfordrv(char *buff, unsigned char d) {
436
  unsigned short r = 0;
437
 
438
  _asm {
439
    /* is d == 0? then I need to resolve current drive */
440
    cmp byte ptr [d], 0
441
    jne GETCWD
442
    /* resolve cur drive */
443
    mov ah, 0x19  /* get current default drive */
444
    int 0x21      /* al = drive (00h = A:, 01h = B:, etc) */
445
    inc al        /* convert to 1=A, 2=B, etc */
446
    mov [d], al
447
 
448
    GETCWD:
449
    /* prepend buff with drive:\ */
450
    mov si, buff
451
    mov dl, [d]
452
    mov [si], dl
453
    add byte ptr [si], 'A' - 1
454
    inc si
455
    mov [si], ':'
456
    inc si
457
    mov [si], '\\'
458
    inc si
459
 
460
    mov ah, 0x47      /* get current directory of drv DL into DS:SI */
461
    int 0x21
462
    jnc DONE
463
    mov [r], ax       /* copy result from ax */
464
 
465
    DONE:
466
  }
467
 
468
  return(r);
469
}
420 mateuszvis 470
 
471
 
472
/* fills a nls_patterns struct with current NLS patterns, returns 0 on success, DOS errcode otherwise */
473
unsigned short nls_getpatterns(struct nls_patterns *p) {
474
  unsigned short r = 0;
475
 
476
  _asm {
477
    mov ax, 0x3800  /* DOS 2+ -- Get Country Info for current country */
478
    mov dx, p       /* DS:DX points to the CountryInfoRec buffer */
479
    int 0x21
480
    jnc DONE
481
    mov [r], ax     /* copy DOS err code to r */
482
    DONE:
483
  }
484
 
485
  return(r);
486
}
487
 
488
 
489
/* computes a formatted date based on NLS patterns found in p
490
 * returns length of result */
491
unsigned short nls_format_date(char *s, unsigned short yr, unsigned char mo, unsigned char dy, const struct nls_patterns *p) {
492
  unsigned short items[3];
493
  /* preset date/month/year in proper order depending on date format */
494
  switch (p->dateformat) {
495
    case 0:  /* USA style: m d y */
496
      items[0] = mo;
497
      items[1] = dy;
498
      items[2] = yr;
499
      break;
500
    case 1:  /* EU style: d m y */
501
      items[0] = dy;
502
      items[1] = mo;
503
      items[2] = yr;
504
      break;
505
    case 2:  /* Japan-style: y m d */
506
    default:
507
      items[0] = yr;
508
      items[1] = mo;
509
      items[2] = dy;
510
      break;
511
  }
512
  /* compute the string */
513
  return(sprintf(s, "%02u%s%02u%s%02u", items[0], p->datesep, items[1], p->datesep, items[2]));
514
}
515
 
516
 
426 mateuszvis 517
/* computes a formatted time based on NLS patterns found in p, sc are ignored if set 0xff
420 mateuszvis 518
 * returns length of result */
426 mateuszvis 519
unsigned short nls_format_time(char *s, unsigned char ho, unsigned char mn, unsigned char sc, const struct nls_patterns *p) {
520
  char ampm = 0;
521
  unsigned short res;
522
 
420 mateuszvis 523
  if (p->timefmt == 0) {
524
    if (ho == 12) {
426 mateuszvis 525
      ampm = 'p';
420 mateuszvis 526
    } else if (ho > 12) {
527
      ho -= 12;
426 mateuszvis 528
      ampm = 'p';
420 mateuszvis 529
    } else { /* ho < 12 */
530
      if (ho == 0) ho = 12;
426 mateuszvis 531
      ampm = 'a';
420 mateuszvis 532
    }
426 mateuszvis 533
    res = sprintf(s, "%2u", ho);
534
  } else {
535
    res = sprintf(s, "%02u", ho);
420 mateuszvis 536
  }
426 mateuszvis 537
 
538
  /* append separator and minutes */
539
  res += sprintf(s + res, "%s%02u", p->timesep, mn);
540
 
541
  /* if seconds provided, append them, too */
542
  if (sc != 0xff) res += sprintf(s + res, "%s%02u", p->timesep, sc);
543
 
544
  /* finally append AM/PM char */
545
  if (ampm != 0) s[res++] = ampm;
546
  s[res] = 0;
547
 
548
  return(res);
420 mateuszvis 549
}
550
 
551
 
552
/* computes a formatted integer number based on NLS patterns found in p
553
 * returns length of result */
423 mateuszvis 554
unsigned short nls_format_number(char *s, unsigned long num, const struct nls_patterns *p) {
555
  unsigned short sl = 0, i;
420 mateuszvis 556
  unsigned char thcount = 0;
557
 
423 mateuszvis 558
  /* write the value (reverse) with thousand separators (if any defined) */
420 mateuszvis 559
  do {
560
    if ((thcount == 3) && (p->thousep[0] != 0)) {
561
      s[sl++] = p->thousep[0];
562
      thcount = 0;
563
    }
564
    s[sl++] = '0' + num % 10;
565
    num /= 10;
566
    thcount++;
567
  } while (num > 0);
568
 
423 mateuszvis 569
  /* terminate the string */
420 mateuszvis 570
  s[sl] = 0;
571
 
423 mateuszvis 572
  /* reverse the string now (has been built in reverse) */
573
  for (i = sl / 2 + (sl & 1); i < sl; i++) {
420 mateuszvis 574
    thcount = s[i];
423 mateuszvis 575
    s[i] = s[sl - (i + 1)];   /* abc'de  if i=3 then ' <-> c */
420 mateuszvis 576
    s[sl - (i + 1)] = thcount;
577
  }
578
 
423 mateuszvis 579
  return(sl);
420 mateuszvis 580
}
437 mateuszvis 581
 
582
 
583
/* reload nls ressources from svarcom.lng into langblock */
584
void nls_langreload(char *buff, unsigned short env) {
585
  unsigned short i;
586
  const char far *nlspath;
587
  char *langblockptr = langblock;
588
  unsigned short lang;
589
  unsigned short errcode = 0;
590
 
591
  /* look up the LANG env variable, upcase it and copy to lang */
592
  nlspath = env_lookup_val(env, "LANG");
593
  if ((nlspath == NULL) || (nlspath[0] == 0)) return;
594
  buff[0] = nlspath[0];
595
  buff[1] = nlspath[1];
596
  buff[2] = 0;
597
 
598
  if (buff[0] >= 'a') buff[0] -= 'a' - 'A';
599
  if (buff[1] >= 'a') buff[1] -= 'a' - 'A';
600
  memcpy(&lang, buff, 2);
601
 
602
  /* check if there is need to reload at all */
603
  if (((unsigned short *)langblock)[0] == lang) return;
604
 
605
  /* printf("NLS RELOAD (curlang=%04X ; toload=%04X\r\n", ((unsigned short *)langblock)[0], lang); */
606
 
607
  nlspath = env_lookup_val(env, "NLSPATH");
608
  if ((nlspath == NULL) || (nlspath[0] == 0)) return;
609
 
610
  /* copy NLSPATH(far) to buff */
611
  for (i = 0; nlspath[i] != 0; i++) buff[i] = nlspath[i];
612
 
613
  /* terminate with a bkslash, if not already the case */
614
  if (buff[i - 1] != '\\') buff[i++] = '\\';
615
 
616
  /* append "svarcom.lng" */
617
  strcpy(buff + i, "SVARCOM.LNG");
618
 
619
  /* copy file content to langblock */
620
  _asm {
621
    push ax
622
    push bx
623
    push cx
624
    push dx
625
    push si
626
    push di
627
 
628
    /* make sure ES=DS and clear DF (will be useful for string matching) */
629
    push ds
630
    pop es
631
    cld
632
 
633
    /* preset SI to buff */
634
    mov si, buff
635
 
636
    /* Open File */
637
    mov bx, 0xffff /* bx holds the file handle (0xffff = not set) */
638
    mov ax, 0x3d00 /* DOS 2+ -- Open File for read */
639
    mov dx, si     /* fname */
640
    int 0x21       /* cf set on error, otherwise handle in AX */
641
    jnc OPENOK
642
    jmp FAIL
643
    OPENOK:
644
    mov bx, ax    /* save file handle to bx */
645
 
646
    /* read hdr */
647
    mov ah, 0x3f   /* DOS 2+ -- Read from File via Handle in BX */
648
    mov cx, 4      /* read 4 bytes */
649
    mov dx, si
650
    int 0x21
651
    jnc READHDROK
652
    jmp FAIL
653
 
654
    READHDROK:
655
 
656
    cmp ax, cx  /* hdr must be 4 bytes long (SvL\x1b) */
657
    jne FAIL
658
    /* check that sig is Svl\x1b */
659
    mov di, si
660
    cld         /* scasw must inc DI */
661
    mov ax, 'vS'
662
    scasw       /* cmp ax, ES:[DI] and DI += 2*/
663
    jne FAIL
664
    mov ax, 0x1B4C
665
    scasw       /* cmp ax, ES:[DI] and DI += 2*/
666
    jne FAIL
667
 
668
    READLANGID:
669
    /* read lang id */
670
    mov ah, 0x3f   /* Read from File via Handle in BX */
671
    /* mov bx, [i]  already set */
672
    mov cx, 4
673
    mov dx, si
674
    int 0x21
675
    jc FAIL
676
    cmp ax, cx
677
    jne FAIL
678
    /* is this the LANG I am looking for? */
679
    mov ax, [lang]
680
    mov di, si
681
    scasw       /* cmp ax, ES:[DI] and DI += 2*/
682
    je LOADSTRINGS
683
    /* skip to next lang */
684
    mov ax, 0x4201   /* move file pointer CX:DX bytes forward */
685
    /* mov bx, [i]  file handle */
686
    xor cx, cx
687
    mov dx, [di]
688
    int 0x21
689
    jc FAIL
690
    jmp READLANGID
691
 
692
    LOADSTRINGS:
693
 
694
    /* copy langid and langlen to langblockptr */
695
    mov di, langblockptr
696
    mov ax, [si]
697
    stosw   /* mov [di], ax and di += 2 */
698
    mov ax, [si+2]
699
    stosw
700
    /* read strings (buff+2 bytes) into langblock */
701
    mov ah, 0x3f   /* Read from File via Handle in BX */
702
    mov cx, [si+2]
703
    mov dx, di
704
    int 0x21
705
    jnc DONE
706
 
707
    /* on error make sure to zero out langblock's header */
708
    xor cx, cx
709
    mov [di], cx                 /* langblock id*/
710
    mov [di + 2], cx /* langblock len */
711
    mov [di + 4], cx /* 1st string id */
712
    mov [di + 6], cx /* 1st string len */
713
 
714
    /* cleanup and quit */
715
    FAIL:
716
    mov [errcode], ax
717
    DONE:
718
    /* close file handle if set */
719
    cmp bx, 0xffff
720
    je FNOTOPEN
721
    mov ah, 0x3e  /* DOS 2+ -- Close a File Handle (Handle in BX) */
722
    int 0x21
723
    FNOTOPEN:
724
 
725
    pop di
726
    pop si
727
    pop dx
728
    pop cx
729
    pop bx
730
    pop ax
731
  }
732
 
733
  if (errcode != 0) printf("AX=%04x\r\n", errcode);
734
}