Subversion Repositories SvarDOS

Compare Revisions

Ignore whitespace Rev 594 → Rev 595

/localcfg/tags/20220202/Makefile
0,0 → 1,33
#
# localcfg Makefile for OpenWatcom
#
 
all: localcfg.com
 
localcfg.com: localcfg.c country.c
wcl -0 -y -cc -wx -mt -lr -zp1 -we -d0 -ox localcfg.c country.c
upx --8086 -9 localcfg.com
 
clean: .SYMBOLIC
del *.obj
del *.com
 
buildpkg: localcfg.com
mkdir appinfo
mkdir source
mkdir source\localcfg
mkdir bin
mkdir doc
copy localcfg.lsm appinfo
copy localcfg.com bin
copy *.txt doc
copy *.c source\localcfg
copy *.h source\localcfg
copy *.txt source\localcfg
copy makefile source\localcfg
zip -9rkDX -m localcfg.zip appinfo bin doc source
rmdir appinfo
rmdir source\localcfg
rmdir source
rmdir bin
rmdir doc
/localcfg/tags/20220202/TODO
0,0 → 1,5
- when file doesn't exist, it should always be created (even if no
modification switch has been used)
- when trying to open a country.sys file that is not localcfg-computed, the
error message should specifically say so
- translation support (svarlang)
/localcfg/tags/20220202/country.c
0,0 → 1,335
/*
* functions that reads/writes from/to the localcfg country.sys-like file.
* Copyright (C) Mateusz Viste 2015-2022
*/
 
#include <stdio.h>
#include <string.h>
 
#include "country.h"
 
 
struct funchdr {
unsigned char funcname[8];
unsigned short funcsiz;
};
 
 
/* fills a country struct with default values */
static void country_default(struct country *countrydata) {
 
/* first clear the memory */
bzero(countrydata, sizeof(struct country));
 
/* fill in CTYINFO fields (non-zero values only) */
countrydata->CTYINFO.id = 1;
countrydata->CTYINFO.codepage = 437;
/* countrydata->CTYINFO.datefmt = COUNTRY_DATE_MDY;
countrydata->CTYINFO.timefmt = COUNTRY_TIME12; */
countrydata->CTYINFO.currsym[0] = '$';
countrydata->CTYINFO.decimal[0] = '.';
countrydata->CTYINFO.thousands[0] = ',';
countrydata->CTYINFO.datesep[0] = '/';
countrydata->CTYINFO.timesep[0] = ':';
countrydata->CTYINFO.currprec = 2;
/* countrydata->CTYINFO.currencydecsym = 0; */
/* countrydata->CTYINFO.currencyspace = 0; */
/* countrydata->CTYINFO.currencypos = 0; */
 
/* fill in YESNO fields (non-zero values only) */
countrydata->YESNO.yes[0] = 'Y';
countrydata->YESNO.no[0] = 'N';
}
 
 
/* Loads data from a country.sys file into a country struct.
* Returns 0 on success, non-zero otherwise. */
int country_read(struct country *countrydata, const char *fname) {
unsigned char filebuff[1024];
short firstentryoffs;
unsigned char *subfunctions[16] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
short filesize;
short subfunctionscount;
unsigned char *functiondata;
int x;
FILE *fd;
 
/* preload the country struct with default values */
country_default(countrydata);
 
/* load the file into buff, if file exists */
fd = fopen(fname, "rb");
if (fd == NULL) return(0); /* "file doesn't exist" is not an error condition */
filesize = fread(filebuff, 1, sizeof(filebuff), fd);
fclose(fd);
 
/* check that it's a country file - should start with 0xFF COUNTRY 0x00 */
if (memcmp(filebuff, "\377COUNTRY\0", 9) != 0) return(-2);
 
/* check that it's one of my country.sys files - should contain a trailer */
if (memcmp(filebuff + filesize - 8, "LOCALCFG", 8) != 0) return(-3);
 
/* read the offset of the entries index - must be at least 23 */
functiondata = filebuff + 19;
firstentryoffs = *((unsigned short *)functiondata);
if ((firstentryoffs < 23) || (firstentryoffs >= filesize)) return(-4);
functiondata = filebuff + firstentryoffs;
 
/* how many entries do we have? I expect exactly one. */
if (*((unsigned short *)functiondata) != 1) return(-5);
/* skip to the first country entry */
functiondata += 2;
 
/* skip directly to the subfunctions of the first country */
/* ddwords: size, country, codepage, reserved, reserved, offset */
/* printf("Size = %d\n", READSHORT(functiondata)); */
functiondata += 2; /* skip size */
/* printf("Country = %d\n", READSHORT(functiondata[0]); */
functiondata += 2; /* skip country */
/* printf("Codepage = %d\n", READSHORT(functiondata)); */
functiondata += 2; /* skip codepage */
functiondata += 4; /* skip reserved fields */
firstentryoffs = *((unsigned short *)functiondata); /* read offset of the subfunctions index */
functiondata = filebuff + firstentryoffs;
 
/* read all subfunctions, but no more than 15 */
subfunctionscount = *((unsigned short *)functiondata);
/* printf("Found %d subfunctions\n", subfunctionscount); */
functiondata += 2;
for (x = 0; (x < 15) && (x < subfunctionscount); x++) {
short size = *((unsigned short *)functiondata);
functiondata += 2;
functiondata += 2; /* skip ID of the subfunction */
subfunctions[x] = filebuff + *((unsigned short *)functiondata);
/* printf("subfunction %d at 0x%p\n", x, subfunctions[x]); */
functiondata += size - 2;
}
 
/* load every subfunction, and feed the country struct with data */
for (x = 0; subfunctions[x] != NULL; x++) {
struct funchdr *hdr = (void *)(subfunctions[x]);
functiondata = subfunctions[x] + 10;
/* */
if ((memcmp(hdr->funcname, "\377YESNO ", 8) == 0) && (hdr->funcsiz == 4)) {
memcpy(&(countrydata->YESNO), functiondata, hdr->funcsiz);
} else if ((memcmp(hdr->funcname, "\377CTYINFO", 8) == 0) && (hdr->funcsiz == 22)) {
memcpy(&(countrydata->CTYINFO), functiondata, hdr->funcsiz);
}
}
 
return(0);
}
 
 
#define MSB(x) (((x) >> 8) & 0xff)
#define LSB(x) ((x) & 0xff)
 
 
/* Computes a new country.sys file based on data from a country struct.
* Returns 0 on success, non-zero otherwise. */
int country_write(const char *fname, struct country *c) {
unsigned char filebuff[1024];
short filesize = 0;
FILE *fd;
int x;
short subfunction_id[7] = {1,2,4,5,6,7,35};
short subfunction_ptr[7];
 
const unsigned char ucase_437[128] = {128, 154, 69, 65, 142, 65, 143, 128,
69, 69, 69, 73, 73, 73, 142, 143,
144, 146, 146, 79, 153, 79, 85, 85,
89, 153, 154, 155, 156, 157, 158, 159,
65, 73, 79, 85, 165, 165, 166, 167,
168, 169, 170, 171, 172, 173, 174, 175,
176, 177, 178, 179, 180, 181, 182, 183,
184, 185, 186, 187, 188, 189, 190, 191,
192, 193, 194, 195, 196, 197, 198, 199,
200, 201, 202, 203, 204, 205, 206, 207,
208, 209, 210, 211, 212, 213, 214, 215,
216, 217, 218, 219, 220, 221, 222, 223,
224, 225, 226, 227, 228, 229, 230, 231,
232, 233, 234, 235, 236, 237, 238, 239,
240, 241, 242, 243, 244, 245, 246, 247,
248, 249, 250, 251, 252, 253, 254, 255};
 
const unsigned char collate_437[256] = { 0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87,
88, 89, 90, 91, 92, 93, 94, 95,
96, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87,
88, 89, 90, 123, 124, 125, 126, 127,
67, 85, 69, 65, 65, 65, 65, 67,
69, 69, 69, 73, 73, 73, 65, 65,
69, 65, 65, 79, 79, 79, 85, 85,
89, 79, 85, 36, 36, 36, 36, 36,
65, 73, 79, 85, 78, 78, 166, 167,
63, 169, 170, 171, 172, 33, 34, 34,
176, 177, 178, 179, 180, 181, 182, 183,
184, 185, 186, 187, 188, 189, 190, 191,
192, 193, 194, 195, 196, 197, 198, 199,
200, 201, 202, 203, 204, 205, 206, 207,
208, 209, 210, 211, 212, 213, 214, 215,
216, 217, 218, 219, 220, 221, 222, 223,
224, 83, 226, 227, 228, 229, 230, 231,
232, 233, 234, 235, 236, 237, 238, 239,
240, 241, 242, 243, 244, 245, 246, 247,
248, 249, 250, 251, 252, 253, 254, 255};
 
/* zero out filebuff */
bzero(filebuff, sizeof(filebuff));
 
/* compute the country.sys structures */
memcpy(filebuff, "\377COUNTRY\0\0\0\0\0\0\0\0\1\0\1", 19); /* header */
filesize = 19;
/* first entry offset (always current offset+4) */
filesize += 4;
memcpy(filebuff + filesize - 4, &filesize, sizeof(filesize));
/* number of entries */
filebuff[filesize] = 1;
filesize += 2;
/* first (and only) entry / size, country, codepage, reserved(2), offset */
filebuff[filesize++] = 12; /* size LSB */
filebuff[filesize++] = 0; /* size MSB */
filebuff[filesize++] = LSB(c->CTYINFO.id); /* country LSB */
filebuff[filesize++] = MSB(c->CTYINFO.id); /* country MSB */
filebuff[filesize++] = LSB(c->CTYINFO.codepage); /* codepage LSB */
filebuff[filesize++] = MSB(c->CTYINFO.codepage); /* codepage MSB */
filesize += 4; /* reserved bytes */
 
filesize += 4;
memcpy(filebuff + filesize - 4, &filesize, sizeof(filesize));
 
/* index of subfunctions */
filebuff[filesize] = 7; /* there are 7 subfunctions */
filesize += 2;
for (x = 0; x < 7; x++) { /* dump each subfunction (size, id, offset) */
/* size is always 6 */
filebuff[filesize] = 6;
filesize += 2;
/* id of the subfunction */
filebuff[filesize++] = LSB(subfunction_id[x]);
filebuff[filesize++] = MSB(subfunction_id[x]);
/* remember the offset of the subfunction pointer for later */
subfunction_ptr[x] = filesize;
filesize += 4;
}
 
/* write the CTYINFO subfunction */
memcpy(filebuff + subfunction_ptr[0], &filesize, sizeof(filesize));
 
/* subfunction header */
memcpy(filebuff + filesize, "\377CTYINFO", 8);
filesize += 8;
/* subfunction size */
filebuff[filesize] = 22;
filesize += 2;
 
/* country preferences */
memcpy(filebuff + filesize, &(c->CTYINFO), 22);
filesize += 22;
 
/* write the UCASE subfunction (used for LCASE, too) */
memcpy(filebuff + subfunction_ptr[1], &filesize, sizeof(filesize));
memcpy(filebuff + subfunction_ptr[2], &filesize, sizeof(filesize));
 
/* subfunction header */
memcpy(filebuff + filesize, "\377UCASE ", 8);
filesize += 8;
/* subfunction size */
filebuff[filesize++] = 128;
filebuff[filesize++] = 0;
/* UCASE table */
memcpy(filebuff + filesize, ucase_437, 128);
filesize += 128;
 
/* write the FCHAR subfunction (filename terminator table) */
memcpy(filebuff + subfunction_ptr[3], &filesize, sizeof(filesize));
 
/* subfunction header */
memcpy(filebuff + filesize, "\377FCHAR ", 8);
filesize += 8;
/* subfunction size */
filebuff[filesize++] = 22;
filebuff[filesize++] = 0;
/* values here are quite obscure, dumped from country.sys */
filebuff[filesize++] = 142;
filebuff[filesize++] = 0;
filebuff[filesize++] = 255;
filebuff[filesize++] = 65;
filebuff[filesize++] = 0;
filebuff[filesize++] = 32;
filebuff[filesize++] = 238;
/* list of characters that terminates a filename */
filebuff[filesize++] = 14; /* how many of them */
filebuff[filesize++] = 46; /* . */
filebuff[filesize++] = 34; /* " */
filebuff[filesize++] = 47; /* / */
filebuff[filesize++] = 92; /* \ */
filebuff[filesize++] = 91; /* [ */
filebuff[filesize++] = 93; /* ] */
filebuff[filesize++] = 58; /* : */
filebuff[filesize++] = 124; /* | */
filebuff[filesize++] = 60; /* < */
filebuff[filesize++] = 62; /* > */
filebuff[filesize++] = 43; /* + */
filebuff[filesize++] = 61; /* = */
filebuff[filesize++] = 59; /* ; */
filebuff[filesize++] = 44; /* , */
 
/* write the COLLATE subfunction */
memcpy(filebuff + subfunction_ptr[4], &filesize, sizeof(filesize));
 
/* subfunction header */
memcpy(filebuff + filesize, "\377COLLATE", 8);
filesize += 8;
/* subfunction size */
filebuff[filesize++] = LSB(256);
filebuff[filesize++] = MSB(256);
/* collation for standard CP437 */
memcpy(filebuff + filesize, collate_437, 256);
filesize += 256;
 
/* write the DBCS subfunction */
memcpy(filebuff + subfunction_ptr[5], &filesize, sizeof(filesize));
/* subfunction header */
memcpy(filebuff + filesize, "\377DBCS ", 8);
filesize += 8;
/* subfunction size */
filebuff[filesize++] = 0;
filebuff[filesize++] = 0;
/* table terminator (must be there even if no lenght is zero */
filebuff[filesize++] = 0;
filebuff[filesize++] = 0;
 
/* write the YESNO subfunction */
memcpy(filebuff + subfunction_ptr[6], &filesize, sizeof(filesize));
memcpy(filebuff + filesize, "\377YESNO ", 8);
filesize += 8;
filebuff[filesize] = 4; /* size (LSB) */
filesize += 2;
memcpy(filebuff + filesize, &(c->YESNO), 4);
filesize += 4;
 
/* write the file trailer */
memcpy(filebuff + filesize, "LOCALCFG", 8);
filesize += 8;
 
/* write the buffer to file */
fd = fopen(fname, "wb");
if (fd == NULL) return(-1);
fwrite(filebuff, 1, filesize, fd);
fclose(fd);
 
return(0);
}
/localcfg/tags/20220202/country.h
0,0 → 1,56
/*
* functions that read/write from/to the localcfg country.sys-like file.
* Copyright (C) Mateusz Viste 2015-2022
*/
 
#ifndef country_h_sentinel
#define country_h_sentinel
 
enum COUNTRY_DATEFMT {
COUNTRY_DATE_MDY = 0, /* Month, Day, Year */
COUNTRY_DATE_DMY = 1, /* Day, Month, Year */
COUNTRY_DATE_YMD = 2 /* Year, Month, Day */
};
 
enum COUNTRY_TIMEFMT {
COUNTRY_TIME12 = 0, /* AM/PM format (like 6:32 PM) */
COUNTRY_TIME24 = 1 /* 24h format (like 18:32) */
};
 
 
 
struct country {
 
struct {
unsigned short id; /* international id (48=PL, 33=FR, 01=US...) */
unsigned short codepage; /* usual codepage */
unsigned short datefmt; /* date format */
char currsym[5]; /* currency symbol */
char thousands[2]; /* thousands separator */
char decimal[2]; /* decimal separator (like . or ,) */
char datesep[2]; /* date separator (usually '-', '.' or '/') */
char timesep[2]; /* time separator (usually ':') */
unsigned char currpos:1; /* 0=currency precedes the value, 1=follows it */
unsigned char currspace:1; /* set if the currency symbol should be one space away from the value */
unsigned char currdecsym:1; /* set if the currency symbol should replace the decimal point */
unsigned char ZEROED:5;
unsigned char currprec; /* currency precision (2 = 0.12) */
unsigned char timefmt; /* time format */
} CTYINFO;
 
struct {
char yes[2];
char no[2];
} YESNO;
 
};
 
/* Loads data from a country.sys file into a country struct.
* Returns 0 on success, non-zero otherwise. */
int country_read(struct country *countrydata, const char *fname);
 
/* Computes a new country.sys file based on data from a country struct.
* Returns 0 on success, non-zero otherwise. */
int country_write(const char *fname, struct country *countrydata);
 
#endif
/localcfg/tags/20220202/history.txt
0,0 → 1,11
localcfg history file
 
* localcfg ver 20220202 (merged into the SvarDOS project)
- country.sys path is optional, defaults to %DOSDIR%\CFG\COUNTRY.SYS
- COUNTRY=xxx example contains the actual codepage and path to COUNTRY.SYS
- improved loading/saving routines so they rely on memory structures
- relicensed from BSD 2-clause to MIT
- moved from Turbo C to OpenWatcom
 
* localcfg v0.90 [02 June 2015]
- first public release
/localcfg/tags/20220202/localcfg.c
0,0 → 1,362
/*
* Locales configuration for SvarDOS
*
* Copyright (C) Mateusz Viste 2015-2022
*
* 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 "country.h"
 
#define PVER "20220202"
#define PDATE "2015-2022"
 
 
static void about(void) {
puts("localcfg ver " PVER " - locales configuration for DOS\n"
"Copyright (C) " PDATE " Mateusz Viste\n"
"\n"
"localcfg creates or edits a custom COUNTRY.SYS-like file with your preferences.\n"
"\n"
"usage: localcfg [COUNTRY.SYS] [options]\n"
"\n"
"options allow to configure country locales to your likening, as follows:\n"
" /country:XX indicates your country code is XX (1 for USA, 33 for France, etc)\n"
" /cp:XXX adapts country data for codepage XXX (example: '437')\n"
" /decim:X reconfigures the decimal symbol to be 'X'");
puts(" /thous:X reconfigures the thousands symbol to be 'X'\n"
" /datesep:X sets the date separator to 'X' (for example '/')\n"
" /datefmt:X sets the date format, can be: MDY, DMY or YMD\n"
" /timesep:X sets the time separator to 'X' (for example ':')\n"
" /timefmt:X sets the time format: 0=12h with AM/PM or 1=24h\n"
" /curr:XXX sets the currency to XXX (a string of 1 to 4 characters)\n"
" /currpos:X sets the currency symbol position to X, where X is either");
puts(" 0=currency precedes the value, 1=currency follows the value and\n"
" 2=currency replaces the decimal sign\n"
" /currspc:X space between the currency and the value (0=no, 1=yes)\n"
" /currprec:X currency's precision (number of decimal digits, 0..9)\n"
" /yesno:XY sets the 'Yes/No' letter to XY (default: YN)\n"
"\n"
"If COUNTRY.SYS location is not provided, then localcfg tries loading it\n"
"from %DOSDIR%\\CFG\\COUNTRY.SYS\n"
);
}
 
 
static char *datestring(struct country *c) {
static char result[16];
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(struct country *c) {
static char result[16];
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(struct country *c) {
static char result[16];
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];
 
/* scan argv looking for the path to country.sys */
for (x = 1; x < argc; x++) {
if (argv[x][0] != '/') {
if (fname[0] != 0) {
puts("ERROR: file path can be provided only once");
return(1);
}
/* */
if (file_truename(fname, argv[x]) != 0) {
puts("ERROR: bad file path");
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) {
printf("ERROR: failed to read the preference file [%d]\n", x);
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) {
puts("ERROR: invalid parameter syntax");
return(3);
}
}
 
printf("Country intl code.....: %03d\n", cntdata.CTYINFO.id);
printf("Codepage..............: %d\n", cntdata.CTYINFO.codepage);
printf("Decimal separator.....: %c\n", cntdata.CTYINFO.decimal[0]);
printf("Thousands separator...: %c\n", cntdata.CTYINFO.thousands[0]);
printf("Date format...........: %s\n", datestring(&cntdata));
printf("Time format...........: %s\n", timestring(&cntdata));
printf("Yes/No letters........: %c/%c\n", cntdata.YESNO.yes[0], cntdata.YESNO.no[0]);
printf("Currency example......: %s\n", currencystring(&cntdata));
 
printf("\n"
"Make sure that your CONFIG.SYS contains this directive:\n"
"COUNTRY=%03d,%03d,%s\n\n", cntdata.CTYINFO.id, cntdata.CTYINFO.codepage, fname);
 
/* if anything changed, write the new file */
if (changedflag != 0) country_write(fname, &cntdata);
 
return(0);
}
/localcfg/tags/20220202/localcfg.lsm
0,0 → 1,2
version: 20220202
description: locales preferences configuration tool (country.sys-like)
/localcfg/tags/20220202/localcfg.txt
0,0 → 1,36
 
localcfg - locales editor for DOS
 
 
*** About ***
 
localcfg is a 16 bit DOS tool that provides an easy way of configurating system
locales by generating a custom COUNTRY.SYS file that reflects user's
preferences.
 
Run localcfg /? for usage instructions.
 
 
*** License ***
 
localcfg is written by Mateusz Viste, and published under the MIT license.
 
Copyright (C) 2015-2022 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.