/* nlmessages.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* */
/* AS-Portierung */
/* */
/* Einlesen und Verwalten von Meldungs-Strings */
/* */
/* Historie: 13. 8.1997 Grundsteinlegung */
/* 17. 8.1997 Verallgemeinerung auf mehrere Kataloge */
/* */
/*****************************************************************************/
#include "stdinc.h"
#include <string.h>
#include "strutil.h"
#include "be_le.h"
#include "bpemu.h"
#include "nls.h"
#include "chardefs.h"
#include "nlmessages.h"
/*****************************************************************************/
static const char *IdentString = "AS Message Catalog - not readable\n\032\004";
static const char *EOpenMsg = "cannot open msg file %s";
static const char *ERdMsg = "cannot read from msg file";
static const char *EIndMsg = "string table index error";
static TMsgCat DefaultCatalog =
{
NULL, NULL, 0
};
typedef struct
{
Byte *p_data;
size_t data_length, rd_ptr;
Boolean data_allocated;
} as_raw_msg_struct_t;
/*!------------------------------------------------------------------------
* \fn free_raw(as_raw_msg_struct_t *p_raw)
* \brief free raw message struct
* \param p_raw struct to free
* ------------------------------------------------------------------------ */
static void free_raw(as_raw_msg_struct_t *p_raw)
{
if (p_raw)
{
if (p_raw->p_data && p_raw->data_allocated)
p_raw->p_data = NULL;
p_raw->data_allocated = False;
p_raw->data_length = p_raw->rd_ptr = 0;
}
}
/*!------------------------------------------------------------------------
* \fn seek_raw(as_raw_msg_struct_t *p_raw, size_t offset, int whence)
* \brief seek in raw message struct
* \param p_raw struct to seek in
* \param offset offset from ref position
* \param whence ref position (like for fseek())
* \return 0 for OK, -1 for error
* ------------------------------------------------------------------------ */
static int seek_raw(as_raw_msg_struct_t *p_raw, size_t offset, int whence)
{
size_t new_pos;
switch (whence)
{
case SEEK_SET: new_pos = offset; break;
case SEEK_CUR: new_pos = p_raw->rd_ptr + offset; break;
case SEEK_END: new_pos = p_raw->data_length + offset; break;
default:
return -1;
}
if (new_pos <= p_raw->data_length)
{
p_raw->rd_ptr = new_pos;
return 0;
}
else
return -1;
}
/*!------------------------------------------------------------------------
* \fn read_raw(void *p_buf, size_t memb_size, size_t n_memb, as_raw_msg_struct_t *p_raw)
* \brief read data from raw message block
* \param p_buf where to read
* \param memb_size size of elements to read
* \param n_memb # of elements to read
* \param p_raw struct to read from
* \return actual # of elements read
* ------------------------------------------------------------------------ */
static size_t read_raw(void *p_buf, size_t memb_size, size_t n_memb, as_raw_msg_struct_t *p_raw)
{
size_t avl = ((p_raw->data_length > p_raw->rd_ptr)
? (p_raw->data_length - p_raw->rd_ptr) : 0) / memb_size,
to_copy;
if (avl < n_memb)
n_memb = avl;
to_copy = n_memb * memb_size;
memcpy(p_buf
, &p_raw
->p_data
[p_raw
->rd_ptr
], to_copy
);
p_raw->rd_ptr += to_copy;
return n_memb;
}
/*!------------------------------------------------------------------------
* \fn read_raw_4(as_raw_msg_struct_t *p_raw, LongInt *p_dest)
* \brief read 32 bit integer from raw message block
* \param p_raw struct to read from
* \param p_dest where to read
* \return True if success
* ------------------------------------------------------------------------ */
static Boolean read_raw_4(as_raw_msg_struct_t *p_raw, LongInt *p_dest)
{
if (read_raw(p_dest, 4, 1, p_raw) != 1)
return False;
if (HostBigEndian)
DSwap(p_dest, 4);
return True;
}
/*!------------------------------------------------------------------------
* \fn create_raw_file(const char *p_file_name)
* \brief create raw message struct from file
* \param p_file_name file path & name
* \return * to struct or NULL if failed
* ------------------------------------------------------------------------ */
static as_raw_msg_struct_t *create_raw_file(const char *p_file_name)
{
FILE *p_file = NULL;
as_raw_msg_struct_t *p_result = NULL;
Boolean success = False;
p_file
= fopen(p_file_name
, OPENRDMODE
);
if (!p_file)
goto func_exit;
p_result
= (as_raw_msg_struct_t
*)calloc(1, sizeof(*p_result
));
if (!p_result)
goto func_exit;
fseek(p_file
, 0, SEEK_END
);
p_result
->data_length
= ftell(p_file
);
if (p_result->data_length >= 65535u)
p_result->data_length = 65535u;
fseek(p_file
, 0, SEEK_SET
);
p_result
->p_data
= (Byte
*)malloc(p_result
->data_length
);
if (p_result->data_length && !p_result->p_data)
goto func_exit;
p_result->data_allocated = True;
if (fread(p_result
->p_data
, 1, p_result
->data_length
, p_file
) != p_result
->data_length
)
goto func_exit;
success = True;
func_exit:
if (p_file)
if (p_result && !success)
{
free_raw(p_result);
p_result = NULL;
}
return p_result;
}
/*****************************************************************************/
static void error(const char *Msg)
{
fprintf(stderr
, "message catalog handling: %s - program terminated\n", Msg
);
}
char *catgetmessage(PMsgCat Catalog, int Num)
{
if ((Num >= 0) && (Num < Catalog->MsgCount))
return Catalog->MsgBlock + Catalog->StrPosis[Num];
else
{
static char *umess = NULL;
if (!umess)
umess
= (char*)malloc(sizeof(char) * STRINGSIZE
);
as_snprintf(umess, STRINGSIZE, "catgetmessage: message number %d does not exist", Num);
return umess;
}
}
char *getmessage(int Num)
{
return catgetmessage(&DefaultCatalog, Num);
}
/*!------------------------------------------------------------------------
* \fn check_header_raw(as_raw_msg_struct_t *p_raw, LongInt msg_id1, LongInt msg_id2)
* \brief check raw msg file for correct header
* \param p_raw file to check
* \param msg_id1, msg_id2 expected identifiers
* \return True if file OK
* ------------------------------------------------------------------------ */
static Boolean check_header_raw(as_raw_msg_struct_t *p_raw, LongInt msg_id1, LongInt msg_id2)
{
String line;
size_t ident_len
= strlen(IdentString
);
LongInt r_id1, r_id2;
return !seek_raw(p_raw, 0, SEEK_SET)
&& (ident_len == read_raw(line, 1, ident_len, p_raw))
&& !memcmp(line
, IdentString
, ident_len
)
&& read_raw_4(p_raw, &r_id1)
&& (r_id1 == msg_id1)
&& read_raw_4(p_raw, &r_id2)
&& (r_id2 == msg_id2);
}
/*!------------------------------------------------------------------------
* \fn open_raw_file_and_check(const char *name, LongInt MsgId1, LongInt MsgId2)
* \brief open raw msg file from file and check header
* \param name file's name & path
* \param MsgId1, MsgId2, expected identifiers
* \return * to raw file or NULL if failed
* ------------------------------------------------------------------------ */
as_raw_msg_struct_t *open_raw_file_and_check(const char *name, LongInt MsgId1, LongInt MsgId2)
{
as_raw_msg_struct_t *p_ret = NULL;
p_ret = create_raw_file(name);
if (!p_ret)
return NULL;
if (!check_header_raw(p_ret, MsgId1, MsgId2))
{
fprintf(stderr
, "message catalog handling: warning: %s has invalid format or is out of date\n", name
);
free_raw(p_ret); p_ret = NULL;
}
return p_ret;
}
/*!------------------------------------------------------------------------
* \fn parse_raw_msg_file(PMsgCat p_cat, as_raw_msg_struct_t *p_msg_raw)
* \brief parse raw message catalog into runtime structure
* \param p_cat where to store result
* \param p_raw raw catalog to parse
* ------------------------------------------------------------------------ */
static void parse_raw_file(PMsgCat p_cat, as_raw_msg_struct_t *p_msg_raw)
{
LongInt DefPos = -1, MomPos, DefLength = 0, MomLength, z, StrStart, CtryCnt, Ctrys[100];
unsigned StrCap;
Boolean fi, Gotcha;
const tNLSCharacterTab *CharacterTab;
char *pStr, str[2048], *ptr;
tNLSCharacter Ch;
const char *lcstring;
Word CountryCode;
/* get reference for finding out which language set to use */
CountryCode = NLS_GetCountryCode();
lcstring
= getenv("LC_MESSAGES");
if (!lcstring)
if (!lcstring)
if (!lcstring)
lcstring = "";
Gotcha = False;
do
{
ptr = str;
do
{
if (read_raw(ptr, 1, 1, p_msg_raw) != 1)
error(ERdMsg);
fi = (*ptr == '\0');
if (!fi) ptr++;
}
while (!fi);
if (*str != '\0')
{
if (!read_raw_4(p_msg_raw, &MomLength))
error(ERdMsg);
if (!read_raw_4(p_msg_raw, &CtryCnt))
error(ERdMsg);
for (z = 0; z < CtryCnt; z++)
if (!read_raw_4(p_msg_raw, Ctrys + z))
error(ERdMsg);
if (!read_raw_4(p_msg_raw, &MomPos))
error(ERdMsg);
if (DefPos == -1)
{
DefPos = MomPos;
DefLength = MomLength;
}
for (z = 0; z < CtryCnt; z++)
if (Ctrys[z] == CountryCode)
Gotcha = True;
if (!Gotcha)
Gotcha
= !as_strncasecmp
(lcstring
, str
, strlen(str
));
}
}
while ((*str != '\0') && (!Gotcha));
if (*str == '\0')
{
MomPos = DefPos;
MomLength = DefLength;
}
/* read pointer table */
seek_raw(p_msg_raw, MomPos, SEEK_SET);
if (!read_raw_4(p_msg_raw, &StrStart))
error(ERdMsg);
p_cat->MsgCount = (StrStart - MomPos) >> 2;
p_cat
->StrPosis
= (LongInt
*) malloc(sizeof(LongInt
)*p_cat
->MsgCount
);
p_cat->StrPosis[0] = 0;
if ((int)read_raw(p_cat->StrPosis + 1, 4, p_cat->MsgCount - 1, p_msg_raw) + 1 != p_cat->MsgCount)
error(ERdMsg);
if (HostBigEndian)
DSwap(p_cat->StrPosis + 1, (p_cat->MsgCount - 1) << 2);
for (z = 1; z < p_cat->MsgCount; z++)
{
p_cat->StrPosis[z] -= StrStart;
if ((p_cat->StrPosis[z] < 0) || (p_cat->StrPosis[z] >= MomLength))
error(EIndMsg);
}
/* read string table */
seek_raw(p_msg_raw, StrStart, SEEK_SET);
p_cat
->MsgBlock
= (char *) malloc(MomLength
);
if ((int)read_raw(p_cat->MsgBlock, 1, MomLength, p_msg_raw) != MomLength)
error(ERdMsg);
/* character replacement according to runtime codepage */
CharacterTab = GetCharacterTab(NLS_GetCodepage());
for (z = 1; z < p_cat->MsgCount; z++)
{
pStr = p_cat->MsgBlock + p_cat->StrPosis[z];
for (Ch = (tNLSCharacter)0; Ch < eCH_cnt; Ch++)
strreplace(pStr, NLS_HtmlCharacterTab[Ch], (*CharacterTab)[Ch], 2, StrCap);
}
}
#define MSGPATHNAME "AS_MSGPATH"
/*!------------------------------------------------------------------------
* \fn msg_catalog_open_file(PMsgCat p_catalog, const char *p_file_name, const char *p_exe_path, LongInt msg_id1, LongInt msg_id2)
* \brief open message catalog from file
* \param p_catalog catalog to fill
* \param p_file_name catalog's file name
* \param p_exe_path executable's path
* \param file_msg_id1, file_msg_id1 file magic values to expect
* ------------------------------------------------------------------------ */
void msg_catalog_open_file(PMsgCat p_catalog, const char *p_file_name, const char *p_exe_path, LongInt msg_id1, LongInt msg_id2)
{
as_raw_msg_struct_t *p_msg_raw;
String str;
char *ptr;
const char *pSep;
/* find first valid message file: current directory has prio 1: */
p_msg_raw = open_raw_file_and_check(p_file_name, msg_id1, msg_id2);
/* if executable path (argv[0]) is given and contains more than just the
plain name, try its path with next-highest prio: */
if (!p_msg_raw && p_exe_path && *p_exe_path)
{
#ifdef __CYGWIN32__
for (ptr = (char*)p_exe_path; *ptr != '\0'; ptr++)
if (*ptr == '/') *ptr = '\\';
#endif
pSep
= strrchr(p_exe_path
, PATHSEP
);
if (pSep)
{
int path_len = pSep - p_exe_path;
as_snprintf(str, sizeof(str), "%*.*s%s%s", path_len, path_len, p_exe_path, SPATHSEP, p_file_name);
p_msg_raw = open_raw_file_and_check(str, msg_id1, msg_id2);
}
}
if (!p_msg_raw)
{
/* AS_MSGPATH has priority over.. */
if (ptr)
{
as_snprintf(str, sizeof(str), "%s%c%s", ptr, PATHSEP, p_file_name);
p_msg_raw = open_raw_file_and_check(str, msg_id1, msg_id2);
}
/* ...PATH: */
else
{
if (ptr)
{
String Dest;
int Result;
#ifdef __CYGWIN32__
DeCygWinDirList(pCopy);
#endif
Result = FSearch(Dest, sizeof(Dest), p_file_name, NULL, ptr);
p_msg_raw = Result ? NULL : open_raw_file_and_check(Dest, msg_id1, msg_id2);
/* If we were found via PATH (no slashes in argv[0]/p_exe_path), look up this
path and replace bin/ with lib/ for 'companion path': */
if (!p_msg_raw
&& !strrchr(p_exe_path
, PATHSEP
) && !FSearch
(Dest
, sizeof(Dest
), p_exe_path
, NULL
, ptr
))
{
char *pSep2;
strreplace(Dest, SPATHSEP "bin", SPATHSEP "lib", 0, sizeof(Dest));
if (pSep2)
*pSep2 = '\0';
strmaxcat(Dest, SPATHSEP, sizeof(Dest));
strmaxcat(Dest, p_file_name, sizeof(Dest));
p_msg_raw = open_raw_file_and_check(Dest, msg_id1, msg_id2);
}
}
}
}
#ifdef LIBDIR
if (!p_msg_raw)
{
as_snprintf(str, sizeof(str), "%s%c%s", LIBDIR, PATHSEP, p_file_name);
p_msg_raw = open_raw_file_and_check(str, msg_id1, msg_id2);
}
#endif
if (!p_msg_raw)
{
as_snprintf(str, sizeof(str), EOpenMsg, p_file_name);
error(str);
}
parse_raw_file(p_catalog, p_msg_raw);
free_raw(p_msg_raw);
}
/*!------------------------------------------------------------------------
* \fn msg_catalog_open_buffer(PMsgCat p_catalog, const unsigned char *p_buffer, size_t buffer_size, LongInt msg_id1, LongInt msg_id2)
* \brief open message catalog from file
* \param p_catalog catalog to fill
* \param p_buffer buffer containing catalog
* \param buffer_size buffer's size
* \param msg_id1, msg_id1 file magic values to expect
* ------------------------------------------------------------------------ */
void msg_catalog_open_buffer(PMsgCat p_catalog, const unsigned char *p_buffer, size_t buffer_size, LongInt msg_id1, LongInt msg_id2)
{
as_raw_msg_struct_t
*p_raw
= (as_raw_msg_struct_t
*)calloc(1, sizeof(*p_raw
));
p_raw->p_data = (Byte*)p_buffer;
p_raw->data_allocated = False;
p_raw->data_length = buffer_size;
p_raw->rd_ptr = 0;
if (!check_header_raw(p_raw, msg_id1, msg_id2))
{
fprintf(stderr
, "message catalog handling: error: catalog'y buffer has invalid format or is out of date\n");
}
parse_raw_file(p_catalog, p_raw);
free_raw(p_raw);
}
/*!------------------------------------------------------------------------
* \fn nlmessages_init_file(const char *p_file_name, char *p_exe_path, LongInt msg_id1, LongInt msg_id2)
* \brief module initialization, load default catalog from file
* \param p_file_name catalog's file name
* \param p_exe_path executable's path
* \param msg_id1, msg_id2 catalog file magic to expect
* ------------------------------------------------------------------------ */
void nlmessages_init_file(const char *p_file_name, char *p_exe_path, LongInt msg_id1, LongInt msg_id2)
{
msg_catalog_open_file(&DefaultCatalog, p_file_name, p_exe_path, msg_id1, msg_id2);
}
/*!------------------------------------------------------------------------
* \fn nlmessages_init_buffer(const unsigned char *p_buffer, size_t buffer_size, LongInt msg_id1, LongInt msg_id2)
* \brief module initialization, load default catalog from buffer
* \param p_buffer buffer containing catalog
* \param buffer_size buffer's size
* \param msg_id1, msg_id2 catalog file magic to expect
* ------------------------------------------------------------------------ */
void nlmessages_init_buffer(const unsigned char *p_buffer, size_t buffer_size, LongInt msg_id1, LongInt msg_id2)
{
msg_catalog_open_buffer(&DefaultCatalog, p_buffer, buffer_size, msg_id1, msg_id2);
}