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
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
|
/*
* Copyright (c) 2001, 2002 Michael J. Roberts. All Rights Reserved.
*
* Please see the accompanying license file, LICENSE.TXT, for information
* on using and copying this software.
*/
/*
Name
resfind.cpp - find a multimedia resource in a tads 2 or tads 3 game file
Function
Searches a compiled tads 2 game file or a compiled tads 3 image file
for a multimedia resource of a given name. The caller doesn't have to
know which tads version created the file; we'll sense the file type and
parse it accordingly.
This implementation is independent of any tads 2 or tads 3 subsystem
except osifc, which it uses for portable file I/O and portable byte
format conversions.
Notes
Modified
09/24/01 MJRoberts - Creation
*/
#include <string.h>
#include <stdlib.h>
#include <os.h>
#include "t3std.h"
#include "resfind.h"
/* ------------------------------------------------------------------------ */
/*
* tads 2 file signature
*/
#define T2_SIG "TADS2 bin\012\015\032"
/*
* tads 3 file signature
*/
#define T3_SIG "T3-image\015\012\032"
/* ------------------------------------------------------------------------ */
/*
* Find a resource in a tads 2 game file
*/
static int t2_find_res(osfildef *fp, const char *resname,
tads_resinfo *info)
{
char buf[300];
unsigned long startpos;
size_t resname_len;
int found;
/* we haven't found what we're looking for yet */
found = FALSE;
/* note the length of the name we're seeking */
resname_len = strlen(resname);
/*
* note the seek location of the start of the tads 2 game file stream
* within the file - if the game file is embedded in a larger file
* stream, the seek locations we find within the file are relative to
* this starting location
*/
startpos = osfpos(fp);
/*
* skip past the tads 2 file header (13 bytes for the signature, 7
* bytes for the version header, 2 bytes for the flags, 26 bytes for
* the timestamp)
*/
osfseek(fp, 13 + 7 + 2 + 26, OSFSK_CUR);
/*
* scan the sections in the file; stop on $EOF, and skip everything
* else but HTMLRES, which is the section type that
*/
for (;;)
{
unsigned long endofs;
/* read the section type and next-section pointer */
if (osfrb(fp, buf, 1)
|| osfrb(fp, buf + 1, (int)((unsigned char)buf[0] + 4)))
{
/* failed to read it - give up */
return FALSE;
}
/* note the ending position of this section */
endofs = t3rp4u(buf + 1 + (unsigned char)buf[0]);
/* check the type */
if (buf[0] == 7 && memcmp(buf+1, "HTMLRES", 7) == 0)
{
unsigned long entry_cnt;
unsigned long i;
/*
* It's a multimedia resource block. Read the index table
* header (which contains the number of entries and a reserved
* uint32).
*/
if (osfrb(fp, buf, 8))
return FALSE;
/* get the number of entries from the header */
entry_cnt = t3rp4u(buf);
/* read the entries */
for (i = 0 ; i < entry_cnt ; ++i)
{
unsigned long res_ofs;
unsigned long res_siz;
unsigned short name_len;
/* read the entry header */
if (osfrb(fp, buf, 10))
return FALSE;
/* parse the header */
res_ofs = t3rp4u(buf);
res_siz = t3rp4u(buf + 4);
name_len = osrp2(buf + 8);
/* read the entry's name */
if (name_len > sizeof(buf) || osfrb(fp, buf, name_len))
return FALSE;
/*
* if it matches the name we're looking for, note that we
* found it
*/
if (name_len == resname_len
&& memicmp(resname, buf, name_len) == 0)
{
/*
* note that we found it, and note its resource size
* and offset in the return structure - but keep
* scanning the rest of the directory, since we need
* to know where the directory ends to know where the
* actual resources begin
*/
found = TRUE;
info->seek_pos = res_ofs;
info->siz = res_siz;
}
}
/*
* if we found our resource, the current seek position is the
* base of the offset we found in the directory; so fix up the
* offset to give the actual file location
*/
if (found)
{
/* fix up the offset with the actual file location */
info->seek_pos += osfpos(fp);
/* tell the caller we found it */
return TRUE;
}
/* we didn't find it - seek to the end of this section */
osfseek(fp, endofs + startpos, OSFSK_SET);
}
else if (buf[0] == 4 && memcmp(buf+1, "$EOF", 4) == 0)
{
/*
* that's the end of the file - we've finished without finding
* the resource, so return failure
*/
return FALSE;
}
else
{
/*
* this isn't a section we're interested in - skip to the end
* of the section and keep going
*/
osfseek(fp, endofs + startpos, OSFSK_SET);
}
}
}
/* ------------------------------------------------------------------------ */
/*
* Find a resource in a T3 image file
*/
static int t3_find_res(osfildef *fp, const char *resname,
tads_resinfo *info)
{
char buf[256];
size_t resname_len;
/* note the length of the name we're seeking */
resname_len = strlen(resname);
/*
* skip the file header - 11 bytes for the signature, 2 bytes for the
* format version, 32 reserved bytes, and 24 bytes for the timestamp
*/
osfseek(fp, 11 + 2 + 32 + 24, OSFSK_CUR);
/* scan the data blocks */
for (;;)
{
unsigned long siz;
/* read the block header */
if (osfrb(fp, buf, 10))
return FALSE;
/* get the block size */
siz = t3rp4u(buf + 4);
/* check the type */
if (memcmp(buf, "MRES", 4) == 0)
{
unsigned long base_ofs;
unsigned int entry_cnt;
unsigned int i;
/*
* remember the current seek position - the data seek location
* for each index entry is given as an offset from this
* location
*/
base_ofs = osfpos(fp);
/* read the number of entries */
if (osfrb(fp, buf, 2))
return FALSE;
/* parse the entry count */
entry_cnt = osrp2(buf);
/* read the entries */
for (i = 0 ; i < entry_cnt ; ++i)
{
unsigned long entry_ofs;
unsigned long entry_siz;
unsigned int entry_name_len;
char *p;
size_t rem;
/* read this index entry's header */
if (osfrb(fp, buf, 9))
return FALSE;
/* parse the header */
entry_ofs = t3rp4u(buf);
entry_siz = t3rp4u(buf + 4);
entry_name_len = (unsigned char)buf[8];
/* read the entry's name */
if (osfrb(fp, buf, entry_name_len))
return FALSE;
/* XOR the bytes of the name with 0xFF */
for (p = buf, rem = entry_name_len ; rem != 0 ; ++p, --rem)
*p ^= 0xFF;
/* if this is the one we're looking for, return it */
if (entry_name_len == resname_len
&& memicmp(resname, buf, resname_len) == 0)
{
/*
* fill in the return information - note that the
* entry offset given in the header is an offset from
* data block's starting location, so fix this up to
* an absolute seek location for the return value
*/
info->seek_pos = base_ofs + entry_ofs;
info->siz = entry_siz;
/* return success */
return TRUE;
}
}
}
else if (memcmp(buf, "EOF ", 4) == 0)
{
/*
* end of file - we've finished without finding the resource,
* so return failure
*/
return FALSE;
}
else
{
/*
* we don't care about anything else - just skip this block
* and keep going; to skip the block, simply seek ahead by the
* size of the block's contents as given in the block header
*/
osfseek(fp, siz, OSFSK_CUR);
}
}
}
/* ------------------------------------------------------------------------ */
/*
* Find a multimedia resource with the given name in the given file. The
* file must be positioned at the start of the tads game file when we're
* invoked - this allows searching for a resource within a game file that
* is embedded in a larger file stream, since we don't care where within
* the osfildef stream the tads game file starts.
*
* Fills in the resource information structure with the seek offset and
* size of the resource in the file and returns true if the resource is
* found; returns false if a resource with the given name doesn't exist in
* the file.
*/
int tads_find_resource_fp(osfildef *fp, const char *resname,
tads_resinfo *info)
{
char buf[12];
/* read the signature */
if (!osfrb(fp, buf, 12))
{
/* seek back to the start of the header */
osfseek(fp, -12, OSFSK_CUR);
/* check which signature we have */
if (memcmp(buf, T2_SIG, strlen(T2_SIG)) == 0)
{
/* it's a tads 2 game file - read it accordingly */
return t2_find_res(fp, resname, info);
}
else if (memcmp(buf, T3_SIG, strlen(T3_SIG)) == 0)
{
/* it's a t3 image file - read it accordingly */
return t3_find_res(fp, resname, info);
}
}
/*
* if we get here, it means either that we couldn't read the
* signature, or that we didn't recognize the signature - in either
* case, we can't parse the file at all, so we can't find the resource
*/
return FALSE;
}
/*
* Find a resource in a file, given the filename.
*
* Fills in the resource information structure with the seek offset and
* size of the resource in the file and returns true if the resource is
* found; returns false if a resource with the given name doesn't exist in
* the file.
*/
int tads_find_resource(const char *fname, const char *resname,
tads_resinfo *info)
{
osfildef *fp;
int found;
/* open the file */
if ((fp = osfoprb(fname, OSFTGAME)) == 0
&& (fp = osfoprb(fname, OSFTT3IMG)) == 0)
{
/* we couldn't open the file, so there's no resource to be found */
return FALSE;
}
/* find the resource in the file */
found = tads_find_resource_fp(fp, resname, info);
/* we're done with the file - close it */
osfcls(fp);
/* return our found or not-found indication */
return found;
}
/* ------------------------------------------------------------------------ */
/*
* Testing main - looks for a given resource in a given file, and copies
* it to standard output if found.
*/
#ifdef TEST
int main(int argc, char **argv)
{
osfildef *fp;
const char *fname;
const char *resname;
tads_resinfo res_info;
unsigned long rem;
/* check usage */
if (argc != 3)
{
fprintf(stderr, "usage: resfind <filename> <resname>\n");
exit(2);
}
/* get the arguments */
fname = argv[1];
resname = argv[2];
/* open the file */
if ((fp = osfoprb(fname, OSFTGAME)) == 0
&& (fp = osfoprb(fname, OSFTT3IMG)) == 0)
{
fprintf(stderr, "unable to open file \"%s\"\n", fname);
exit(2);
}
/* find the resource */
if (!tads_find_resource_fp(fp, resname, &res_info))
{
fprintf(stderr, "unable to find resource \"%s\"\n", resname);
osfcls(fp);
exit(1);
}
/* seek to the resource */
osfseek(fp, res_info.seek_pos, OSFSK_SET);
/* copy the resource to standard output */
for (rem = res_info.siz ; rem != 0 ; )
{
char buf[1024];
size_t cur;
/*
* read up to the buffer size or up to the resource size
* remaining, whichever is smaller
*/
cur = sizeof(buf);
if (cur > rem)
cur = (size_t)rem;
/* read the chunk */
if (osfrb(fp, buf, cur))
{
fprintf(stderr, "error reading %u bytes from file\n", cur);
osfcls(fp);
exit(2);
}
/* copy the chunk to standard output */
fwrite(buf, cur, 1, stdout);
/* deduct the amount we just read from the size remaining */
rem -= cur;
}
/* done with the file - close it */
osfcls(fp);
/* success */
exit(0);
return 0;
}
#endif /* TEST */
|