Rev 530 | Rev 571 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
/* This file is part of the SvarCOM project and is published under the terms
* of the MIT license.
*
* Copyright (C) 2021 Mateusz Viste
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/*
* a variety of helper functions
* Copyright (C) 2021 Mateusz Viste
*/
#include <i86.h> /* MK_FP() */
#include <stdio.h> /* sprintf() */
#include <string.h> /* memcpy() */
#include "deflang.h"
#include "env.h"
#include "helpers.h"
/* case-insensitive comparison of strings, compares up to maxlen characters.
* returns non-zero on equality. */
int imatchlim(const char *s1, const char *s2, unsigned short maxlen) {
while (maxlen--) {
char c1, c2;
c1 = *s1;
c2 = *s2;
if ((c1 >= 'a') && (c1 <= 'z')) c1 -= ('a' - 'A');
if ((c2 >= 'a') && (c2 <= 'z')) c2 -= ('a' - 'A');
/* */
if (c1 != c2) return(0);
if (c1 == 0) break;
s1++;
s2++;
}
return(1);
}
/* returns zero if s1 starts with s2 */
int strstartswith(const char *s1, const char *s2) {
while (*s2 != 0) {
if (*s1 != *s2) return(-1);
s1++;
s2++;
}
return(0);
}
/* outputs a NULL-terminated string to handle (1=stdout 2=stderr) */
void output_internal(const char *s, unsigned char nl, unsigned char handle) {
const static unsigned char *crlf = "\r\n";
_asm {
push ds
pop es /* make sure es=ds (scasb uses es) */
/* get length of s into CX */
mov ax, 0x4000 /* ah=DOS "write to file" and AL=0 for NULL matching */
mov dx, s /* set dx to string (required for later) */
mov di, dx /* set di to string (for NULL matching) */
mov cx, 0xffff /* preset cx to 65535 (-1) */
cld /* clear DF so scasb increments DI */
repne scasb /* cmp al, es:[di], inc di, dec cx until match found */
/* CX contains (65535 - strlen(s)) now */
not cx /* reverse all bits so I get (strlen(s) + 1) */
dec cx /* this is CX length */
jz WRITEDONE /* do nothing for empty strings */
/* output by writing to stdout */
/* mov ah, 0x40 */ /* DOS 2+ -- write to file via handle */
xor bh, bh
mov bl, handle /* set handle (1=stdout 2=stderr) */
/* mov cx, xxx */ /* write CX bytes */
/* mov dx, s */ /* DS:DX is the source of bytes to "write" */
int 0x21
WRITEDONE:
/* print out a CR/LF trailer if nl set */
test byte ptr [nl], 0xff
jz FINITO
/* bx still contains handle */
mov ah, 0x40 /* "write to file" */
mov cx, 2
mov dx, crlf
int 0x21
FINITO:
}
}
static const char *nlsblock_findstr(unsigned short id) {
const char *ptr = langblock + 4; /* first 4 bytes are lang id and lang len */
/* find the string id in langblock memory */
for (;;) {
if (((unsigned short *)ptr)[0] == id) {
ptr += 3;
return(ptr);
}
if (ptr[2] == 0) return(NULL);
ptr += ptr[2] + 3;
}
}
void nls_output_internal(unsigned short id, unsigned char nl) {
const char *NOTFOUND = "NLS_STRING_NOT_FOUND";
const char *ptr = nlsblock_findstr(id);
if (ptr == NULL) ptr = NOTFOUND;
output_internal(ptr, nl, hSTDOUT);
}
/* output DOS error e to stderr */
void nls_outputnl_doserr(unsigned short e) {
static char errstr[16];
const char *ptr = NULL;
/* find string in nls block */
if (e < 0xff) ptr = nlsblock_findstr(0xff00 | e);
/* if not found, use a fallback */
if (ptr == NULL) {
sprintf(errstr, "DOS ERR %u", e);
ptr = errstr;
}
/* display */
output_internal(ptr, 1, hSTDERR);
}
/* find first matching files using a FindFirst DOS call
* returns 0 on success or a DOS err code on failure */
unsigned short findfirst(struct DTA *dta, const char *pattern, unsigned short attr) {
unsigned short res = 0;
_asm {
/* set DTA location */
mov ah, 0x1a
mov dx, dta
int 0x21
/* */
mov ah, 0x4e /* FindFirst */
mov dx, pattern
mov cx, attr
int 0x21 /* CF set on error + err code in AX, DTA filled with FileInfoRec on success */
jnc DONE
mov [res], ax
DONE:
}
return(res);
}
/* find next matching, ie. continues an action intiated by findfirst() */
unsigned short findnext(struct DTA *dta) {
unsigned short res = 0;
_asm {
mov ah, 0x4f /* FindNext */
mov dx, dta
int 0x21 /* CF set on error + err code in AX, DTA filled with FileInfoRec on success */
jnc DONE
mov [res], ax
DONE:
}
return(res);
}
/* print s string and wait for a single key press from stdin. accepts only
* key presses defined in the c ASCIIZ string. returns offset of pressed key
* in string. keys in c MUST BE UPPERCASE! */
unsigned short askchoice(const char *s, const char *c) {
unsigned short res;
char key = 0;
AGAIN:
output(s);
output(" ");
_asm {
push ax
push dx
mov ax, 0x0c01 /* clear input buffer and execute getchar (INT 21h,AH=1) */
int 0x21
/* if AL == 0 then this is an extended character */
test al, al
jnz GOTCHAR
mov ah, 0x08 /* read again to flush extended char from input buffer */
int 0x21
xor al, al /* all extended chars are ignored */
GOTCHAR: /* received key is in AL now */
mov [key], al /* save key */
/* print a cr/lf */
mov ah, 0x02
mov dl, 0x0D
int 0x21
mov dl, 0x0A
int 0x21
pop dx
pop ax
}
/* ucase() result */
if ((key >= 'a') && (key <= 'z')) key -= ('a' - 'A');
/* is there a match? */
for (res = 0; c[res] != 0; res++) if (c[res] == key) return(res);
goto AGAIN;
}
/* converts a path to its canonic representation, returns 0 on success
* or DOS err on failure (invalid drive) */
unsigned short file_truename(const char *src, char *dst) {
unsigned short res = 0;
_asm {
push es
mov ah, 0x60 /* query truename, DS:SI=src, ES:DI=dst */
push ds
pop es
mov si, src
mov di, dst
int 0x21
jnc DONE
mov [res], ax
DONE:
pop es
}
return(res);
}
/* returns DOS attributes of file, or -1 on error */
int file_getattr(const char *fname) {
int res = -1;
_asm {
mov ax, 0x4300 /* query file attributes, fname at DS:DX */
mov dx, fname
int 0x21 /* CX=attributes if CF=0, otherwise AX=errno */
jc DONE
mov [res], cx
DONE:
}
return(res);
}
/* returns screen's width (in columns) */
unsigned short screen_getwidth(void) {
/* BIOS 0040:004A = word containing screen width in text columns */
unsigned short far *scrw = MK_FP(0x40, 0x4a);
return(*scrw);
}
/* returns screen's height (in rows) */
unsigned short screen_getheight(void) {
/* BIOS 0040:0084 = byte containing maximum valid row value (EGA ONLY) */
unsigned char far *scrh = MK_FP(0x40, 0x84);
if (*scrh == 0) return(25); /* pre-EGA adapter */
return(*scrh + 1);
}
/* displays the "Press any key to continue" msg and waits for a keypress */
void press_any_key(void) {
nls_output(15, 1); /* Press any key to continue... */
_asm {
mov ah, 0x08 /* no echo console input */
int 0x21 /* pressed key in AL now (0 for extended keys) */
test al, al
jnz DONE
int 0x21 /* executed ah=8 again to read the rest of extended key */
DONE:
/* output CR/LF */
mov ah, 0x02
mov dl, 0x0D
int 0x21
mov dl, 0x0A
int 0x21
}
}
/* validate a drive (A=0, B=1, etc). returns 1 if valid, 0 otherwise */
int isdrivevalid(unsigned char drv) {
_asm {
mov ah, 0x19 /* query default (current) disk */
int 0x21 /* drive in AL (0=A, 1=B, etc) */
mov ch, al /* save current drive to ch */
/* try setting up the drive as current */
mov ah, 0x0E /* select default drive */
mov dl, [drv] /* 0=A, 1=B, etc */
int 0x21
/* this call does not set CF on error, I must check cur drive to look for success */
mov ah, 0x19 /* query default (current) disk */
int 0x21 /* drive in AL (0=A, 1=B, etc) */
mov [drv], 1 /* preset result as success */
cmp al, dl /* is eq? */
je DONE
mov [drv], 0 /* fail */
jmp FAILED
DONE:
/* set current drive back to what it was initially */
mov ah, 0x0E
mov dl, ch
int 0x21
FAILED:
}
return(drv);
}
/* converts a 8+3 filename into 11-bytes FCB format (MYFILE EXT) */
void file_fname2fcb(char *dst, const char *src) {
unsigned short i;
/* fill dst with 11 spaces and a NULL terminator */
for (i = 0; i < 11; i++) dst[i] = ' ';
dst[11] = 0;
/* copy fname until dot (.) or 8 characters */
for (i = 0; i < 8; i++) {
if ((src[i] == '.') || (src[i] == 0)) break;
dst[i] = src[i];
}
/* advance src until extension or end of string */
src += i;
for (;;) {
if (*src == '.') {
src++; /* next character is extension */
break;
}
if (*src == 0) break;
}
/* copy extension to dst (3 chars max) */
dst += 8;
for (i = 0; i < 3; i++) {
if (src[i] == 0) break;
dst[i] = src[i];
}
}
/* converts a 11-bytes FCB filename (MYFILE EXT) into 8+3 format (MYFILE.EXT) */
void file_fcb2fname(char *dst, const char *src) {
unsigned short i, end = 0;
for (i = 0; i < 8; i++) {
dst[i] = src[i];
if (dst[i] != ' ') end = i + 1;
}
/* is there an extension? */
if (src[8] == ' ') {
dst[end] = 0;
} else { /* found extension: copy it until first space */
dst[end++] = '.';
for (i = 8; i < 11; i++) {
if (src[i] == ' ') break;
dst[end++] = src[i];
}
dst[end] = 0;
}
}
/* converts an ASCIIZ string into an unsigned short. returns 0 on success.
* on error, result will contain all valid digits that were read until
* error occurred (0 on overflow or if parsing failed immediately) */
int atous(unsigned short *r, const char *s) {
int err = 0;
_asm {
mov si, s
xor ax, ax /* general purpose register */
xor cx, cx /* contains the result */
mov bx, 10 /* used as a multiplicative step */
NEXTBYTE:
xchg cx, ax /* move result into cx temporarily */
lodsb /* AL = DS:[SI++] */
/* is AL 0? if so we're done */
test al, al
jz DONE
/* validate that AL is in range '0'-'9' */
sub al, '0'
jc FAIL /* invalid character detected */
cmp al, 9
jg FAIL /* invalid character detected */
/* restore result into AX (CX contains the new digit) */
xchg cx, ax
/* multiply result by 10 and add cl */
mul bx /* DX AX = AX * BX(10) */
jc OVERFLOW /* overflow */
add ax, cx
/* if CF is set then overflow occurred (overflow part lands in DX) */
jnc NEXTBYTE
OVERFLOW:
xor cx, cx /* make sure result is zeroed in case overflow occured */
FAIL:
inc [err]
DONE: /* save result (CX) into indirect memory address r */
mov bx, [r]
mov [bx], cx
}
return(err);
}
/* appends a backslash if path is a directory
* returns the (possibly updated) length of path */
unsigned short path_appendbkslash_if_dir(char *path) {
unsigned short len;
int attr;
for (len = 0; path[len] != 0; len++);
if (len == 0) return(0);
if (path[len - 1] == '\\') return(len);
/* */
attr = file_getattr(path);
if ((attr > 0) && (attr & DOS_ATTR_DIR)) {
path[len++] = '\\';
path[len] = 0;
}
return(len);
}
/* get current path drive d (A=1, B=2, etc - 0 is "current drive")
* returns 0 on success, doserr otherwise */
unsigned short curpathfordrv(char *buff, unsigned char d) {
unsigned short r = 0;
_asm {
/* is d == 0? then I need to resolve current drive */
cmp byte ptr [d], 0
jne GETCWD
/* resolve cur drive */
mov ah, 0x19 /* get current default drive */
int 0x21 /* al = drive (00h = A:, 01h = B:, etc) */
inc al /* convert to 1=A, 2=B, etc */
mov [d], al
GETCWD:
/* prepend buff with drive:\ */
mov si, buff
mov dl, [d]
mov [si], dl
add byte ptr [si], 'A' - 1
inc si
mov [si], ':'
inc si
mov [si], '\\'
inc si
mov ah, 0x47 /* get current directory of drv DL into DS:SI */
int 0x21
jnc DONE
mov [r], ax /* copy result from ax */
DONE:
}
return(r);
}
/* fills a nls_patterns struct with current NLS patterns, returns 0 on success, DOS errcode otherwise */
unsigned short nls_getpatterns(struct nls_patterns *p) {
unsigned short r = 0;
_asm {
mov ax, 0x3800 /* DOS 2+ -- Get Country Info for current country */
mov dx, p /* DS:DX points to the CountryInfoRec buffer */
int 0x21
jnc DONE
mov [r], ax /* copy DOS err code to r */
DONE:
}
return(r);
}
/* computes a formatted date based on NLS patterns found in p
* returns length of result */
unsigned short nls_format_date(char *s, unsigned short yr, unsigned char mo, unsigned char dy, const struct nls_patterns *p) {
unsigned short items[3];
/* preset date/month/year in proper order depending on date format */
switch (p->dateformat) {
case 0: /* USA style: m d y */
items[0] = mo;
items[1] = dy;
items[2] = yr;
break;
case 1: /* EU style: d m y */
items[0] = dy;
items[1] = mo;
items[2] = yr;
break;
case 2: /* Japan-style: y m d */
default:
items[0] = yr;
items[1] = mo;
items[2] = dy;
break;
}
/* compute the string */
return(sprintf(s, "%02u%s%02u%s%02u", items[0], p->datesep, items[1], p->datesep, items[2]));
}
/* computes a formatted time based on NLS patterns found in p, sc are ignored if set 0xff
* returns length of result */
unsigned short nls_format_time(char *s, unsigned char ho, unsigned char mn, unsigned char sc, const struct nls_patterns *p) {
char ampm = 0;
unsigned short res;
if (p->timefmt == 0) {
if (ho == 12) {
ampm = 'p';
} else if (ho > 12) {
ho -= 12;
ampm = 'p';
} else { /* ho < 12 */
if (ho == 0) ho = 12;
ampm = 'a';
}
res = sprintf(s, "%2u", ho);
} else {
res = sprintf(s, "%02u", ho);
}
/* append separator and minutes */
res += sprintf(s + res, "%s%02u", p->timesep, mn);
/* if seconds provided, append them, too */
if (sc != 0xff) res += sprintf(s + res, "%s%02u", p->timesep, sc);
/* finally append AM/PM char */
if (ampm != 0) s[res++] = ampm;
s[res] = 0;
return(res);
}
/* computes a formatted integer number based on NLS patterns found in p
* returns length of result */
unsigned short nls_format_number(char *s, unsigned long num, const struct nls_patterns *p) {
unsigned short sl = 0, i;
unsigned char thcount = 0;
/* write the value (reverse) with thousand separators (if any defined) */
do {
if ((thcount == 3) && (p->thousep[0] != 0)) {
s[sl++] = p->thousep[0];
thcount = 0;
}
s[sl++] = '0' + num % 10;
num /= 10;
thcount++;
} while (num > 0);
/* terminate the string */
s[sl] = 0;
/* reverse the string now (has been built in reverse) */
for (i = sl / 2 + (sl & 1); i < sl; i++) {
thcount = s[i];
s[i] = s[sl - (i + 1)]; /* abc'de if i=3 then ' <-> c */
s[sl - (i + 1)] = thcount;
}
return(sl);
}
/* reload nls ressources from svarcom.lng into langblock */
void nls_langreload(char *buff, unsigned short env) {
unsigned short i;
const char far *nlspath;
char *langblockptr = langblock;
unsigned short lang;
unsigned short errcode = 0;
/* look up the LANG env variable, upcase it and copy to lang */
nlspath = env_lookup_val(env, "LANG");
if ((nlspath == NULL) || (nlspath[0] == 0)) return;
buff[0] = nlspath[0];
buff[1] = nlspath[1];
buff[2] = 0;
if (buff[0] >= 'a') buff[0] -= 'a' - 'A';
if (buff[1] >= 'a') buff[1] -= 'a' - 'A';
memcpy(&lang, buff, 2);
/* check if there is need to reload at all */
if (((unsigned short *)langblock)[0] == lang) return;
/* printf("NLS RELOAD (curlang=%04X ; toload=%04X\r\n", ((unsigned short *)langblock)[0], lang); */
nlspath = env_lookup_val(env, "NLSPATH");
if ((nlspath == NULL) || (nlspath[0] == 0)) return;
/* copy NLSPATH(far) to buff */
for (i = 0; nlspath[i] != 0; i++) buff[i] = nlspath[i];
/* terminate with a bkslash, if not already the case */
if (buff[i - 1] != '\\') buff[i++] = '\\';
/* append "svarcom.lng" */
strcpy(buff + i, "SVARCOM.LNG");
/* copy file content to langblock */
_asm {
push ax
push bx
push cx
push dx
push si
push di
/* make sure ES=DS and clear DF (will be useful for string matching) */
push ds
pop es
cld
/* preset SI to buff */
mov si, buff
/* Open File */
mov bx, 0xffff /* bx holds the file handle (0xffff = not set) */
mov ax, 0x3d00 /* DOS 2+ -- Open File for read */
mov dx, si /* fname */
int 0x21 /* cf set on error, otherwise handle in AX */
jnc OPENOK
jmp FAIL
OPENOK:
mov bx, ax /* save file handle to bx */
/* read hdr */
mov ah, 0x3f /* DOS 2+ -- Read from File via Handle in BX */
mov cx, 4 /* read 4 bytes */
mov dx, si
int 0x21
jnc READHDROK
jmp FAIL
READHDROK:
cmp ax, cx /* hdr must be 4 bytes long (SvL\x1b) */
jne FAIL
/* check that sig is Svl\x1b */
mov di, si
cld /* scasw must inc DI */
mov ax, 'vS'
scasw /* cmp ax, ES:[DI] and DI += 2*/
jne FAIL
mov ax, 0x1B4C
scasw /* cmp ax, ES:[DI] and DI += 2*/
jne FAIL
READLANGID:
/* read lang id */
mov ah, 0x3f /* Read from File via Handle in BX */
/* mov bx, [i] already set */
mov cx, 4
mov dx, si
int 0x21
jc FAIL
cmp ax, cx
jne FAIL
/* is this the LANG I am looking for? */
mov ax, [lang]
mov di, si
scasw /* cmp ax, ES:[DI] and DI += 2*/
je LOADSTRINGS
/* skip to next lang */
mov ax, 0x4201 /* move file pointer CX:DX bytes forward */
/* mov bx, [i] file handle */
xor cx, cx
mov dx, [di]
int 0x21
jc FAIL
jmp READLANGID
LOADSTRINGS:
/* copy langid and langlen to langblockptr */
mov di, langblockptr
mov ax, [si]
stosw /* mov [di], ax and di += 2 */
mov ax, [si+2]
stosw
/* read strings (buff+2 bytes) into langblock */
mov ah, 0x3f /* Read from File via Handle in BX */
mov cx, [si+2]
mov dx, di
int 0x21
jnc DONE
/* on error make sure to zero out langblock's header */
xor cx, cx
mov [di], cx /* langblock id*/
mov [di + 2], cx /* langblock len */
mov [di + 4], cx /* 1st string id */
mov [di + 6], cx /* 1st string len */
/* cleanup and quit */
FAIL:
mov [errcode], ax
DONE:
/* close file handle if set */
cmp bx, 0xffff
je FNOTOPEN
mov ah, 0x3e /* DOS 2+ -- Close a File Handle (Handle in BX) */
int 0x21
FNOTOPEN:
pop di
pop si
pop dx
pop cx
pop bx
pop ax
}
if (errcode != 0) printf("AX=%04x\r\n", errcode);
}