Rev 1262 | Go to most recent revision | Blame | Last modification | View Log | RSS feed
/*
* Locales configuration for SvarDOS
*
* Copyright (C) Mateusz Viste 2015-2023
*
* MIT license
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h> /* atoi() */
#include <string.h> /* strchr */
#include "svarlang.h"
#include "country.h"
#define PVER "20230630"
#define PDATE "2015-2023"
enum NLS_STRINGS {
NLS_HLP_VER = 0x0000,
NLS_HLP_DESC = 0x0001,
NLS_HLP_USAGE = 0x0002,
NLS_HLP_OPTIONS = 0x0003,
NLS_HLP_COUNTRY = 0x000A,
NLS_HLP_CP = 0x000B,
NLS_HLP_DECIM = 0x000C,
NLS_HLP_THOUS = 0x000D,
NLS_HLP_DATESEP = 0x000E,
NLS_HLP_DATEFMT = 0x000F,
NLS_HLP_TIMESEP = 0x0010,
NLS_HLP_TIMEFMT = 0x0011,
NLS_HLP_CURR = 0x0012,
NLS_HLP_CURRPOS0 = 0x0013,
NLS_HLP_CURRPOS1 = 0x0014,
NLS_HLP_CURRPOS2 = 0x0015,
NLS_HLP_CURRSPC = 0x0016,
NLS_HLP_CURRPREC = 0x0017,
NLS_HLP_YESNO = 0x0018,
NLS_HLP_INFOLOC1 = 0x0032,
NLS_HLP_INFOLOC2 = 0x0033,
NLS_INFO_COUNTRY = 0x0700,
NLS_INFO_CODEPAGE = 0x0701,
NLS_INFO_DECSEP = 0x0702,
NLS_INFO_THOUSEP = 0x0703,
NLS_INFO_DATEFMT = 0x0704,
NLS_INFO_TIMEFMT = 0x0705,
NLS_INFO_YESNO = 0x0706,
NLS_INFO_CURREXAMPLE = 0x0707,
NLS_MAKESURE = 0x0709,
NLS_ERR_FILEPATHTWICE = 0x0900,
NLS_ERR_BADPATH = 0x0901,
NLS_ERR_READFAIL = 0x0902,
NLS_ERR_INVPARAM = 0x0903,
NLS_ERR_INVFORMAT = 0x0904,
NLS_ERR_NOTLOCALCFG = 0x0905
};
static void output(const char *s) {
_asm {
/* set cx to strlen(s) */
push ds
pop es
mov di, s
xor al, al
cld
mov cx, 0xff
repne scasb /* compare ES:DI with AL, inc DI until match */
mov cx, di
sub cx, s
dec cx
/* output via DOS */
mov ah, 0x40 /* write to handle */
mov bx, 1 /* 1=stdout */
mov dx, s
int 0x21
}
}
static void crlf(void) {
output("\r\n");
}
static void outputnl(const char *s) {
output(s);
crlf();
}
static void nls_put(enum NLS_STRINGS id) {
output(svarlang_strid(id));
}
static void nls_puts(enum NLS_STRINGS id) {
nls_put(id);
crlf();
}
static void about(void) {
output("localcfg ");
nls_put(NLS_HLP_VER);
outputnl(" " PVER ", (C) " PDATE " Mateusz Viste");
nls_puts(NLS_HLP_DESC);
crlf();
nls_puts(NLS_HLP_USAGE);
crlf();
nls_puts(NLS_HLP_OPTIONS);
crlf();
nls_puts(NLS_HLP_COUNTRY);
nls_puts(NLS_HLP_CP);
nls_puts(NLS_HLP_DECIM);
nls_puts(NLS_HLP_THOUS);
nls_puts(NLS_HLP_DATESEP);
nls_puts(NLS_HLP_DATEFMT);
nls_puts(NLS_HLP_TIMESEP);
nls_puts(NLS_HLP_TIMEFMT);
nls_puts(NLS_HLP_CURR);
nls_puts(NLS_HLP_CURRPOS0);
nls_puts(NLS_HLP_CURRPOS1);
nls_puts(NLS_HLP_CURRPOS2);
nls_puts(NLS_HLP_CURRSPC);
nls_puts(NLS_HLP_CURRPREC);
nls_puts(NLS_HLP_YESNO);
crlf();
nls_puts(NLS_HLP_INFOLOC1);
nls_puts(NLS_HLP_INFOLOC2);
}
static char *datestring(char *result, struct country *c) {
switch (c->CTYINFO.datefmt) {
case COUNTRY_DATE_MDY:
sprintf(result, "12%c31%c1990", c->CTYINFO.datesep[0], c->CTYINFO.datesep[0]);
break;
case COUNTRY_DATE_DMY:
sprintf(result, "31%c12%c1990", c->CTYINFO.datesep[0], c->CTYINFO.datesep[0]);
break;
case COUNTRY_DATE_YMD:
default:
sprintf(result, "1990%c12%c31", c->CTYINFO.datesep[0], c->CTYINFO.datesep[0]);
break;
}
return(result);
}
static char *timestring(char *result, struct country *c) {
if (c->CTYINFO.timefmt == COUNTRY_TIME12) {
sprintf(result, "11%c59%c59 PM", c->CTYINFO.timesep[0], c->CTYINFO.timesep[0]);
} else {
sprintf(result, "23%c59%c59", c->CTYINFO.timesep[0], c->CTYINFO.timesep[0]);
}
return(result);
}
static char *currencystring(char *result, struct country *c) {
char decimalpart[16];
char space[2] = {0, 0};
char decsym[8];
char cursym[8];
decimalpart[0] = '1';
decimalpart[1] = '2';
decimalpart[2] = '3';
decimalpart[3] = '4';
decimalpart[4] = '5';
decimalpart[5] = '6';
decimalpart[6] = '7';
decimalpart[7] = '8';
decimalpart[8] = '9';
decimalpart[9] = 0;
/* prepare the decimal string first */
if (c->CTYINFO.currprec < 9) {
decimalpart[c->CTYINFO.currprec] = 0;
}
/* prepare the currency space string */
if (c->CTYINFO.currspace != 0) {
space[0] = ' ';
}
/* prepare the currency and decimal symbols */
if (c->CTYINFO.currdecsym != 0) { /* currency replaces the decimal point */
sprintf(decsym, "%s", c->CTYINFO.currsym);
cursym[0] = 0;
} else {
sprintf(decsym, "%c", c->CTYINFO.decimal[0]);
sprintf(cursym, "%s", c->CTYINFO.currsym);
}
if (c->CTYINFO.currprec == 0) decsym[0] = 0;
/* compute the final string */
if (c->CTYINFO.currpos == 0) { /* currency precedes value */
sprintf(result, "%s%s99%s%s", cursym, space, decsym, decimalpart);
} else { /* currency follows value or replaces decimal symbol */
sprintf(result, "99%s%s%s%s", decsym, decimalpart, space, cursym);
}
return(result);
}
/* checks if str starts with prefix. returns 0 if so, non-zero otherwise. */
static int stringstartswith(char *str, char *prefix) {
for (;;) {
/* end of prefix means success */
if (*prefix == 0) return(0);
/* otherwise there is no match */
if (*str != *prefix) return(-1);
/* if match good so far, look at next char */
str += 1;
prefix += 1;
}
}
/* processes an argument. returns 0 on success, non-zero otherwise. */
static int processarg(char *arg, struct country *c) {
char *value;
int intvalue;
/* an option must start with a '/' */
if (arg[0] != '/') return(-1);
arg += 1; /* skip the slash */
/* find where the value starts */
value = strchr(arg, ':');
/* if no value present, fail */
if (value == NULL) return(-2);
value += 1;
if (*value == 0) return(-3);
/* interpret the option now */
if (stringstartswith(arg, "country:") == 0) {
intvalue = atoi(value);
if ((intvalue > 0) && (intvalue < 1000)) {
c->CTYINFO.id = intvalue;
return(0);
}
} else if (stringstartswith(arg, "cp:") == 0) {
intvalue = atoi(value);
if ((intvalue > 0) && (intvalue < 1000)) {
c->CTYINFO.codepage = intvalue;
return(0);
}
} else if (stringstartswith(arg, "decim:") == 0) {
if (value[1] == 0) { /* value must be exactly one character */
c->CTYINFO.decimal[0] = *value;
return(0);
}
} else if (stringstartswith(arg, "thous:") == 0) {
if (value[1] == 0) { /* value must be exactly one character */
c->CTYINFO.thousands[0] = *value;
return(0);
}
} else if (stringstartswith(arg, "datesep:") == 0) {
if (value[1] == 0) { /* value must be exactly one character */
c->CTYINFO.datesep[0] = *value;
return(0);
}
} else if (stringstartswith(arg, "timesep:") == 0) {
if (value[1] == 0) { /* value must be exactly one character */
c->CTYINFO.timesep[0] = *value;
return(0);
}
} else if (stringstartswith(arg, "datefmt:") == 0) {
if (strcmp(value, "MDY") == 0) {
c->CTYINFO.datefmt = COUNTRY_DATE_MDY;
return(0);
} else if (strcmp(value, "DMY") == 0) {
c->CTYINFO.datefmt = COUNTRY_DATE_DMY;
return(0);
} else if (strcmp(value, "YMD") == 0) {
c->CTYINFO.datefmt = COUNTRY_DATE_YMD;
return(0);
}
} else if (stringstartswith(arg, "timefmt:") == 0) {
if (value[1] == 0) {
if ((value[0] >= '0') && (value[0] <= '1')) {
c->CTYINFO.timefmt = value[0] - '0';
return(0);
}
}
} else if (stringstartswith(arg, "curr:") == 0) {
if (strlen(value) <= 4) {
strcpy(c->CTYINFO.currsym, value);
return(0);
}
} else if (stringstartswith(arg, "currpos:") == 0) {
if (value[1] == 0) {
if (value[0] == '0') {
c->CTYINFO.currpos = 0;
return(0);
} else if (value[0] == '1') {
c->CTYINFO.currpos = 1;
return(0);
} else if (value[0] == '2') {
c->CTYINFO.currpos = 0;
c->CTYINFO.currdecsym = 1;
return(0);
}
}
} else if (stringstartswith(arg, "currspc:") == 0) {
if (value[1] == 0) {
if ((value[0] >= '0') && (value[0] <= '1')) {
c->CTYINFO.currspace = value[0] - '0';
return(0);
}
}
} else if (stringstartswith(arg, "currprec:") == 0) {
if (value[1] == 0) {
if ((value[0] >= '0') && (value[0] <= '9')) {
c->CTYINFO.currprec = value[0] - '0';
return(0);
}
}
} else if (stringstartswith(arg, "yesno:") == 0) {
/* string must be exactly 2 characters long */
if ((value[0] != 0) && (value[1] != 0) && (value[2] == 0)) {
c->YESNO.yes[0] = value[0];
c->YESNO.no[0] = value[1];
return(0);
}
}
/* if I'm here, something went wrong */
return(-4);
}
/* converts a path to its canonic representation, returns 0 on success
* or DOS err on failure (invalid drive) */
static unsigned short file_truename(const char *dst, char *src) {
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);
}
static void default_country_path(char *s) {
char *dosdir = getenv("DOSDIR");
size_t dosdirlen;
s[0] = 0;
if (dosdir == NULL) return;
dosdirlen = strlen(dosdir);
if (dosdirlen == 0) return;
/* drop trailing backslash if present */
if (dosdir[dosdirlen - 1] == '\\') dosdirlen--;
/* copy dosdir to s and append the rest of the path */
memcpy(s, dosdir, dosdirlen);
strcpy(s + dosdirlen, "\\CFG\\COUNTRY.SYS");
}
int main(int argc, char **argv) {
struct country cntdata;
int changedflag;
int x;
static char fname[130];
static char buff[64];
svarlang_autoload_exepath(argv[0], getenv("LANG"));
/* scan argv looking for the path to country.sys */
for (x = 1; x < argc; x++) {
if (argv[x][0] != '/') {
if (fname[0] != 0) {
nls_puts(NLS_ERR_FILEPATHTWICE);
return(1);
}
/* */
if (file_truename(fname, argv[x]) != 0) {
nls_puts(NLS_ERR_BADPATH);
return(1);
}
} else if (strcmp(argv[x], "/?") == 0) { /* is it /? */
about();
return(1);
}
}
/* if no file path provided, look into %DOSDIR%\CFG\COUNTRY.SYS */
if (fname[0] == 0) default_country_path(fname);
x = country_read(&cntdata, fname);
if (x != 0) {
if (x == COUNTRY_ERR_INV_FORMAT) {
nls_puts(NLS_ERR_INVFORMAT);
} else if (x == COUNTRY_ERR_NOT_LOCALCFG) {
nls_puts(NLS_ERR_NOTLOCALCFG);
} else {
nls_puts(NLS_ERR_READFAIL);
}
return(2);
}
changedflag = 0;
/* process command line arguments */
for (x = 1; x < argc; x++) {
if (argv[x][0] != '/') continue; /* skip country.sys filename (processed earlier) */
changedflag++;
if (processarg(argv[x], &cntdata) != 0) {
nls_puts(NLS_ERR_INVPARAM);
return(3);
}
}
nls_put(NLS_INFO_COUNTRY);
sprintf(buff, " %03d", cntdata.CTYINFO.id);
outputnl(buff);
nls_put(NLS_INFO_CODEPAGE);
sprintf(buff, " %d", cntdata.CTYINFO.codepage);
outputnl(buff);
nls_put(NLS_INFO_DECSEP);
sprintf(buff, " %c", cntdata.CTYINFO.decimal[0]);
outputnl(buff);
nls_put(NLS_INFO_THOUSEP);
sprintf(buff, " %c", cntdata.CTYINFO.thousands[0]);
outputnl(buff);
nls_put(NLS_INFO_DATEFMT);
output(" ");
outputnl(datestring(buff, &cntdata));
nls_put(NLS_INFO_TIMEFMT);
output(" ");
outputnl(timestring(buff, &cntdata));
nls_put(NLS_INFO_YESNO);
sprintf(buff, " %c/%c", cntdata.YESNO.yes[0], cntdata.YESNO.no[0]);
outputnl(buff);
nls_put(NLS_INFO_CURREXAMPLE);
output(" ");
outputnl(currencystring(buff, &cntdata));
crlf();
nls_puts(NLS_MAKESURE);
sprintf(buff, "COUNTRY=%03d,%03d,", cntdata.CTYINFO.id, cntdata.CTYINFO.codepage);
output(buff);
outputnl(fname);
/* if anything changed, write the new file */
if (changedflag != 0) country_write(fname, &cntdata);
return(0);
}