aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/tads/tads2/tads2_cmap.cpp
blob: 68945f73367add1bc55f1a9c2626226014a8c7af (plain)
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
256
257
258
259
260
261
262
263
264
265
266
267
268
/* 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "glk/tads/tads2/tads2.h"
#include "glk/tads/tads2/types.h"

namespace Glk {
namespace TADS {
namespace TADS2 {

/**
 * Signatures for character map files.  The signature is stored at the
 * beginning of the file.
 */
// single-byte character map version 1.0.0
#define CMAP_SIG_S100  "TADS2 charmap S100\n\r\01a"

void TADS2::cmap_init_default() {
	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
	strcpy(G_cmap_ldesc, "(native/no mapping)");

	// note that we have no character set loaded
	S_cmap_loaded = false;
}

int TADS2::cmap_load_internal(const 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 == 0) {
		// initialize with the default mapping
		cmap_init_default();

		// return success
		return 0;
	}

	// open the file
	fp = osfoprb(filename, OSFTCMAP);
	if (fp == 0)
		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 (;;) {
			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
			len = osrp2(buf);
			cval = osrp2(buf+2);

			// if we've reached the zero marker, we're done
			if (len == 0 && cval == 0)
				break;

			// read the string
			if (len > CMAP_MAX_ENTITY_EXPANSION
				|| osfrb(fp, expansion, len)) {
				osfcls(fp);
				return 10;
			}

			// tell the output code about the expansion
			tio_set_html_expansion(cval, expansion, len);
		}
	}

	/*
	 * 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;
}

int TADS2::cmap_load(const 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;
}

void TADS2::cmap_override() {
	// 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;
}

void TADS2::cmap_set_game_charset(errcxdef *ec, const char *internal_id,
		const char *internal_ldesc, const 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.
	 */
	strcpy(G_cmap_ldesc, internal_ldesc);
}

} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk