Files
scummvm-cursorfix/engines/glk/tads/tads2/character_map.cpp
2026-02-02 04:50:13 +01:00

340 lines
8.9 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/tads/tads2/character_map.h"
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/error.h"
#include "glk/tads/tads2/os.h"
#include "glk/tads/tads2/text_io.h"
#include "glk/tads/os_frob_tads.h"
#include "glk/tads/os_glk.h"
#include "common/algorithm.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
/* ------------------------------------------------------------------------ */
/*
* Global variables for character mapping tables
*/
unsigned char G_cmap_input[256];
unsigned char G_cmap_output[256];
char G_cmap_id[5];
char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1];
/*
* static variables
*/
/*
* flag: true -> a character set has been explicitly loaded, so we
* should ignore any game character set setting
*/
static int S_cmap_loaded;
/* ------------------------------------------------------------------------ */
/*
* Initialize the default character mappings
*/
void cmap_init_default(void)
{
size_t i;
/* initialize the input table */
for (i = 0 ; i < sizeof(G_cmap_input)/sizeof(G_cmap_input[0]) ; ++i)
G_cmap_input[i] = (unsigned char)i;
/* initialize the output table */
for (i = 0 ; i < sizeof(G_cmap_output)/sizeof(G_cmap_output[0]) ; ++i)
G_cmap_output[i] = (unsigned char)i;
/* we have a null ID */
memset(G_cmap_id, 0, sizeof(G_cmap_id));
/* indicate that it's the default */
Common::strcpy_s(G_cmap_ldesc, "(native/no mapping)");
/* note that we have no character set loaded */
S_cmap_loaded = FALSE;
}
/* ------------------------------------------------------------------------ */
/*
* Internal routine to load a character map from a file
*/
static int cmap_load_internal(char *filename)
{
osfildef *fp;
static char sig1[] = CMAP_SIG_S100;
char buf[256];
uchar lenbuf[2];
size_t len;
int sysblk;
/* if there's no mapping file, use the default mapping */
if (filename == nullptr)
{
/* initialize with the default mapping */
cmap_init_default();
/* return success */
return 0;
}
/* open the file */
fp = osfoprb(filename, OSFTCMAP);
if (fp == nullptr)
return 1;
/* check the signature */
if (osfrb(fp, buf, sizeof(sig1))
|| memcmp(buf, sig1, sizeof(sig1)) != 0)
{
osfcls(fp);
return 2;
}
/* load the ID */
G_cmap_id[4] = '\0';
if (osfrb(fp, G_cmap_id, 4))
{
osfcls(fp);
return 3;
}
/* load the long description */
if (osfrb(fp, lenbuf, 2)
|| (len = osrp2(lenbuf)) > sizeof(G_cmap_ldesc)
|| osfrb(fp, G_cmap_ldesc, len))
{
osfcls(fp);
return 4;
}
/* load the two tables - input, then output */
if (osfrb(fp, G_cmap_input, sizeof(G_cmap_input))
|| osfrb(fp, G_cmap_output, sizeof(G_cmap_output)))
{
osfcls(fp);
return 5;
}
/* read the next section header */
if (osfrb(fp, buf, 4))
{
osfcls(fp);
return 6;
}
/* if it's "SYSI", read the system information string */
if (!memcmp(buf, "SYSI", 4))
{
/* read the length prefix, then the string */
if (osfrb(fp, lenbuf, 2)
|| (len = osrp2(lenbuf)) > sizeof(buf)
|| osfrb(fp, buf, len))
{
osfcls(fp);
return 7;
}
/* we have a system information block */
sysblk = TRUE;
}
else
{
/* there's no system information block */
sysblk = FALSE;
}
/*
* call the OS code, so that it can do any system-dependent
* initialization for the new character mapping
*/
os_advise_load_charmap(G_cmap_id, G_cmap_ldesc, sysblk ? buf : "");
/* read the next section header */
if (sysblk && osfrb(fp, buf, 4))
{
osfcls(fp);
return 8;
}
/* see if we have an entity list */
if (!memcmp(buf, "ENTY", 4))
{
/* read the entities */
for (;;)
{
size_t blen;
unsigned int cval;
char expansion[CMAP_MAX_ENTITY_EXPANSION];
/* read the next item's length and character value */
if (osfrb(fp, buf, 4))
{
osfcls(fp);
return 9;
}
/* decode the values */
blen = osrp2(buf);
cval = osrp2(buf+2);
/* if we've reached the zero marker, we're done */
if (blen == 0 && cval == 0)
break;
/* read the string */
if (blen > CMAP_MAX_ENTITY_EXPANSION
|| osfrb(fp, expansion, blen))
{
osfcls(fp);
return 10;
}
/* tell the output code about the expansion */
tio_set_html_expansion(cval, expansion, blen);
}
}
/*
* ignore anything else we find - if the file format is updated to
* include extra information in the future, and this old code tries
* to load an updated file, we'll just ignore the new information,
* which should always be placed after the "SYSI" block (if present)
* to ensure compatibility with past versions (such as this code)
*/
/* no problems - close the file and return success */
osfcls(fp);
return 0;
}
/* ------------------------------------------------------------------------ */
/*
* Explicitly load a character set from a file. This character set
* mapping will override any implicit character set mapping that we read
* from a game file. This should be called when the player explicitly
* loads a character set (via a command line option or similar action).
*/
int cmap_load(char *filename)
{
int err;
/* try loading the file */
if ((err = cmap_load_internal(filename)) != 0)
return err;
/*
* note that we've explicitly loaded a character set, if they named
* a character set (if not, this simply establishes the default
* setting, so we haven't explicitly loaded anything)
*/
if (filename != nullptr)
S_cmap_loaded = TRUE;
/* success */
return 0;
}
/* ------------------------------------------------------------------------ */
/*
* Explicitly override any game character set and use no character set
* instead.
*/
void cmap_override(void)
{
/* apply the default mapping */
cmap_init_default();
/*
* pretend we have a character map loaded, so that we don't try to
* load another one if the game specifies a character set
*/
S_cmap_loaded = TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Set the game's internal character set. This is called when a game is
* loaded, and the game specifies a character set.
*/
void cmap_set_game_charset(errcxdef *ec,
char *internal_id, char *internal_ldesc,
char *argv0)
{
char filename[OSFNMAX];
/*
* If a character set is already explicitly loaded, ignore the
* game's character set - the player asked us to use a particular
* mapping, so ignore what the game wants. (This will probably
* result in incorrect display of non-ASCII character values, but
* the player is most likely to use this to avoid errors when an
* appropriate mapping file for the game is not available. In this
* case, the player informs us by setting the option that he or she
* knows and accepts that the game will not look exactly right.)
*/
if (S_cmap_loaded)
return;
/*
* ask the operating system to name the mapping file -- this routine
* will determine, if possible, the current native character set,
* and apply a system-specific naming convention to tell us what
* mapping file we should open
*/
os_gen_charmap_filename(filename, internal_id, argv0);
/* try loading the mapping file */
if (cmap_load_internal(filename))
errsig2(ec, ERR_CHRNOFILE,
ERRTSTR, errstr(ec, filename, strlen(filename)),
ERRTSTR, errstr(ec, internal_ldesc, strlen(internal_ldesc)));
/*
* We were successful - the game's internal character set is now
* mapped to the current native character set. Even though we
* loaded an ldesc from the mapping file, forget that and store the
* internal ldesc that the game specified. The reason we do this is
* that it's possible that the player will dynamically switch native
* character sets in the future, at which point we'll need to
* re-load the mapping table, which could raise an error if a
* mapping file for the new character set isn't available. So, we
* may need to provide the same explanation later that we needed to
* provide here. Save the game's character set ldesc for that
* eventuality, since it describes exactly what the *game* wanted.
*/
Common::strcpy_s(G_cmap_ldesc, internal_ldesc);
}
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk