aboutsummaryrefslogtreecommitdiff
path: root/devtools/extract_mort/extract_mort.cpp
blob: 4346f1f4bf7b461272d63924f2951dd8140c85bc (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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
/* 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.
 *
 * This is a utility for extracting needed resource data from different language
 * version of the Lure of the Temptress lure.exe executable files into a new file
 * lure.dat - this file is required for the ScummVM  Lure of the Temptress module
 * to work properly
 */

// Disable symbol overrides so that we can use system headers.
#define FORBIDDEN_SYMBOL_ALLOW_ALL

// HACK to allow building with the SDL backend on MinGW
// see bug #1800764 "TOOLS: MinGW tools building broken"
#ifdef main
#undef main
#endif // main

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "common/endian.h"

enum AccessMode {
	kFileReadMode  = 1,
	kFileWriteMode = 2
};

class File {
private:
	FILE *f;
public:
	bool open(const char *filename, AccessMode mode = kFileReadMode) {
		f = fopen(filename, (mode == kFileReadMode) ? "rb" : "wb");
		return (f != NULL);
	}
	void close() {
		fclose(f);
		f = NULL;
	}
	int seek(int32 offset, int whence = SEEK_SET) {
		return fseek(f, offset, whence);
	}
	long read(void *buffer, int len) {
		return fread(buffer, 1, len, f);
	}
	void write(const void *buffer, int len) {
		fwrite(buffer, 1, len, f);
	}
	byte readByte() {
		byte v;
		read(&v, sizeof(byte));
		return v;
	}
	uint16 readWord() {
		uint16 v;
		read(&v, sizeof(uint16));
		return FROM_LE_16(v);
	}
	uint32 readLong() {
		uint32 v;
		read(&v, sizeof(uint32));
		return FROM_LE_32(v);
	}
	void readString(char *sLine) {
		while ((*sLine = readByte()) != '\n')
			++sLine;

		*sLine = '\0';
	}
	void writeByte(byte v) {
		write(&v, sizeof(byte));
	}
	void writeWord(uint16 v) {
		uint16 vTemp = TO_LE_16(v);
		write(&vTemp, sizeof(uint16));
	}
	void writeLong(uint32 v) {
		uint32 vTemp = TO_LE_32(v);
		write(&vTemp, sizeof(uint32));
	}
	void writeString(const char *s) {
		fprintf(f, "%s", s);
	}
	uint32 pos() {
		return ftell(f);
	}
	uint32 size() {
		int position = ftell(f);
		fseek (f, 0, SEEK_END);
		int end = ftell (f);
		fseek (f, position, SEEK_SET);

		return end;
	}
};

File textFile, txxInp, txxNtp;
int _version;

/*-------------------------------------------------------------------------*/

#define BUFFER_SIZE 32768

const byte tabdrFr[32] = {
	32, 101, 115,  97, 114, 105, 110,
	117, 116, 111, 108,  13, 100,  99,
	112, 109,  46, 118, 130,  39, 102,
	98,  44, 113, 104, 103,  33,  76,
	85, 106,  30,  31
};

const byte tab30Fr[32] = {
	69,  67,  74, 138, 133, 120,  77, 122,
	121,  68,  65,  63,  73,  80,  83,  82,
	156,  45,  58,  79,  49,  86,  78,  84,
	71,  81,  64,  66, 135,  34, 136,  91
};

const byte tab31Fr[32]= {
	93,  47,  48,  53,  50,  70, 124,  75,
	72, 147, 140, 150, 151,  57,  56,  51,
	107, 139,  55,  89, 131,  37,  54,  88,
	119,   0,   0,   0,   0,   0,   0,   0
};

const byte tabdrDe[32] = {
	0x20, 0x65, 0x6E, 0x69, 0x73, 0x72, 0x74,
	0x68, 0x61, 0x75, 0x0D, 0x63, 0x6C, 0x64,
	0x6D, 0x6F, 0x67, 0x2E, 0x62, 0x66, 0x53,
	0x2C, 0x77, 0x45, 0x7A, 0x6B, 0x44, 0x76,
	0x9C, 0x47, 0x1E, 0x1F
};

const byte tab30De[32] = {
	0x49, 0x4D, 0x21, 0x42, 0x4C, 0x70, 0x41, 0x52,
	0x57, 0x4E, 0x48, 0x3F, 0x46, 0x50, 0x55, 0x4B,
	0x5A, 0x4A, 0x54, 0x31, 0x4F, 0x56, 0x79, 0x3A,
	0x6A, 0x5B, 0x5D, 0x40, 0x22, 0x2F, 0x30, 0x35
};

const byte tab31De[32]= {
	0x78, 0x2D, 0x32, 0x82, 0x43, 0x39, 0x33, 0x38,
	0x7C, 0x27, 0x37, 0x3B, 0x25, 0x28, 0x29, 0x36,
	0x51, 0x59, 0x71, 0x81, 0x87, 0x88, 0x93, 0,
	0,    0,    0,    0,    0,    0,    0,    0
};

const byte *tabdr, *tab30, *tab31;
uint16 ctrlChar;

/**
 * Extracts a single character from the game data
 */
static void extractCharacter(unsigned char &c, uint &idx, uint &pt, bool &the_end, const uint16 *strData) {
	uint16 oct, ocd;

	/* 5-8 */
	oct = FROM_LE_16(strData[idx]);

	oct = ((uint16)(oct << (16 - pt))) >> (16 - pt);
	if (pt < 6) {
		idx = idx + 1;
		oct = oct << (5 - pt);
		pt = pt + 11;
		oct = oct | (FROM_LE_16(strData[idx]) >> pt);
	} else {
		pt = pt - 5;
		oct = (uint)oct >> pt;
	}

	if (oct == ctrlChar) {
		c = '$';
		the_end = true;
	} else if (oct == 30 || oct == 31) {
		ocd = FROM_LE_16(strData[idx]);
		ocd = (uint16)(ocd << (16 - pt)) >> (16 - pt);
		if (pt < 6) {
			idx = idx + 1;
			ocd = ocd << (5 - pt);
			pt = pt + 11;
			ocd = ocd | (FROM_LE_16(strData[idx]) >> pt);
		} else {
			pt = pt - 5;
			ocd = (uint)ocd >> pt;
		}
		if (oct == 30)
			c = (char)tab30[ocd];
		else
			c = (char)tab31[ocd];

		if (c == '\0')
			the_end = true;
	} else {
		c = (char)tabdr[oct];
	}
}

/**
 * Puts a compressed 5-bit value into the string data buffer
 */
static void addCompressedValue(int oct, int &indis, int &point, uint16 *strData) {
	// Write out the part of the value that fits into the current word
	if (point < 5)
		strData[indis] |= oct >> (5 - point);
	else
		strData[indis] |= oct << (point - 5);

	// Handling of there's any overlap into the next word
	if (point < 5) {
		// Overlapping into next word
		++indis;

		// Get the bits that fall into the next word and set it
		int remainder = oct & ((1 << (5 - point)) - 1);
		strData[indis] |= remainder << (16 - (5 - point));

		point += -5 + 16;
	} else {
		point -= 5;
		if (point == 0) {
			point = 16;
			++indis;
		}
	}
}

/**
 * Compresses a single passed character and stores it in the compressed strings buffer
 */
static void compressCharacter(unsigned char ch, int &indis, int &point, uint16 *strData) {
	if (ch == '$') {
		// End of string
		addCompressedValue(11, indis, point, strData);
		return;
	}

	// Scan through the tabdr array for a match
	for (int idx = 0; idx < 30; ++idx) {
		if ((idx != 11) && (tabdr[idx] == ch)) {
			addCompressedValue(idx, indis, point, strData);
			return;
		}
	}

	// Scan through the tab30 array
	for (int idx = 0; idx < 32; ++idx) {
		if (tab30[idx] == ch) {
			addCompressedValue(30, indis, point, strData);
			addCompressedValue(idx, indis, point, strData);
			return;
		}
	}

	// Scan through the tab31 array
	for (int idx = 0; idx < 32; ++idx) {
		if (tab31[idx] == ch) {
			addCompressedValue(31, indis, point, strData);
			addCompressedValue(idx, indis, point, strData);
			return;
		}
	}

	printf("Encountered invalid character '%c' when compressing strings\n", ch);
	exit(1);
}

/**
 * string extractor
 */
static void export_strings(const char *textFilename) {
	char buffer[BUFFER_SIZE];
	uint16 *strData;

	// Open input and output files
	if (!txxInp.open("TXX.INP", kFileReadMode)) {
		if (!txxInp.open("TXX.MOR", kFileReadMode)) {
			printf("Missing TXX.INP/MOR");
			exit(-1);
		}
	}
	if (!txxNtp.open("TXX.NTP", kFileReadMode)) {
		if (!txxNtp.open("TXX.IND", kFileReadMode)) {
			printf("Missing TXX.NTP/IND");
			exit(-1);
		}
	}
	textFile.open(textFilename, kFileWriteMode);

	// Read all the compressed string data into a buffer
	printf("%d %d", txxInp.size(), txxNtp.size());
	strData = (uint16 *)malloc(txxInp.size());
	txxInp.read(strData, txxInp.size());

	// Loop through getting each string
	for (unsigned int strIndex = 0; strIndex < (txxNtp.size() / 3); ++strIndex) {
		uint indis = txxNtp.readWord();
		uint point = txxNtp.readByte();

		// Extract the string
		int charIndex = 0;
		unsigned char ch;
		bool endFlag = false;
		do {
			extractCharacter(ch, indis, point, endFlag, strData);
			buffer[charIndex++] = ch;
			if (charIndex == BUFFER_SIZE) {
				printf("Extracted string exceeded allowed buffer size.\n");
				exit(1);
			}

			if (indis >= (txxInp.size() / 2))
				endFlag = true;
		} while (!endFlag);

		// Write out the string
		buffer[charIndex++] = '\n';
		buffer[charIndex] = '\0';
		textFile.writeString(buffer);
	}

	// Close the files and free the buffer
	free(strData);
	txxInp.close();
	txxNtp.close();
	textFile.close();
}

/**
 * string importer
 */
static void import_strings(const char *textFilename) {
	// Open input and output files
	if (!txxInp.open("TXX.INP", kFileWriteMode)) {
		printf("Missing TXX data file");
		exit(-1);
	}
	if (!txxNtp.open("TXX.NTP", kFileWriteMode)) {
		printf("Missing TXX index file");
		exit(-1);
	}
	textFile.open(textFilename, kFileReadMode);

	// Set up a buffer for the output compressed strings
	uint16 strData[BUFFER_SIZE];
	memset(strData, 0, BUFFER_SIZE);
	char sLine[BUFFER_SIZE];

	int indis = 0;
	int point = 16;

	while (textFile.pos() < textFile.size()) {
		// Read in the next source line
		textFile.readString(sLine);

		// Write out the index entry for the string
		txxNtp.writeWord(indis);
		txxNtp.writeByte(point);

		// Loop through writing out the characters to the compressed data buffer
		char *s = sLine;
		while (*s) {
			compressCharacter(*s, indis, point, strData);
			++s;
		}
	}

	// Write out the compressed data
	if (point != 16)
		++indis;
	txxInp.write(strData, indis * 2);

	// Close the files
	txxInp.close();
	txxNtp.close();
	textFile.close();
}


int main(int argc, char *argv[]) {
	if (argc != 4) {
		printf("Format: %s export|import v1|v2 output_file\n", argv[0]);
		printf("where:\nv1: French DOS version\nv2: German DOS version\n");
		printf("The program must be run from the directory with the Mortville Manor game files.\n");
		exit(0);
	}

	if (!strcmp(argv[2], "v1")) {
		tab30 = tab30Fr;
		tab31 = tab31Fr;
		tabdr = tabdrFr;
		ctrlChar = 11;
	} else if (!strcmp(argv[2], "v2")) {
		tab30 = tab30De;
		tab31 = tab31De;
		tabdr = tabdrDe;
		ctrlChar = 10;
	} else {
		printf("Unknown version");
		exit(-1);
	}

	// Do the processing
	if (!strcmp(argv[1], "export"))
		export_strings(argv[3]);
	else if (!strcmp(argv[1], "import"))
		import_strings(argv[3]);
	else
		printf("Unknown operation specified\n");
}