340 lines
8.9 KiB
C++
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
|