2
* Copyright (c) 2001, 2002 Michael J. Roberts. All Rights Reserved.
4
* Please see the accompanying license file, LICENSE.TXT, for information
5
* on using and copying this software.
9
resfind.cpp - find a multimedia resource in a tads 2 or tads 3 game file
11
Searches a compiled tads 2 game file or a compiled tads 3 image file
12
for a multimedia resource of a given name. The caller doesn't have to
13
know which tads version created the file; we'll sense the file type and
16
This implementation is independent of any tads 2 or tads 3 subsystem
17
except osifc, which it uses for portable file I/O and portable byte
22
09/24/01 MJRoberts - Creation
32
/* ------------------------------------------------------------------------ */
34
* tads 2 file signature
36
#define T2_SIG "TADS2 bin\012\015\032"
39
* tads 3 file signature
41
#define T3_SIG "T3-image\015\012\032"
45
/* ------------------------------------------------------------------------ */
47
* Find a resource in a tads 2 game file
49
static int t2_find_res(osfildef *fp, const char *resname,
53
unsigned long startpos;
57
/* we haven't found what we're looking for yet */
60
/* note the length of the name we're seeking */
61
resname_len = strlen(resname);
64
* note the seek location of the start of the tads 2 game file stream
65
* within the file - if the game file is embedded in a larger file
66
* stream, the seek locations we find within the file are relative to
67
* this starting location
69
startpos = osfpos(fp);
72
* skip past the tads 2 file header (13 bytes for the signature, 7
73
* bytes for the version header, 2 bytes for the flags, 26 bytes for
76
osfseek(fp, 13 + 7 + 2 + 26, OSFSK_CUR);
79
* scan the sections in the file; stop on $EOF, and skip everything
80
* else but HTMLRES, which is the section type that
86
/* read the section type and next-section pointer */
88
|| osfrb(fp, buf + 1, (int)((unsigned char)buf[0] + 4)))
90
/* failed to read it - give up */
94
/* note the ending position of this section */
95
endofs = t3rp4u(buf + 1 + (unsigned char)buf[0]);
98
if (buf[0] == 7 && memcmp(buf+1, "HTMLRES", 7) == 0)
100
unsigned long entry_cnt;
104
* It's a multimedia resource block. Read the index table
105
* header (which contains the number of entries and a reserved
108
if (osfrb(fp, buf, 8))
111
/* get the number of entries from the header */
112
entry_cnt = t3rp4u(buf);
114
/* read the entries */
115
for (i = 0 ; i < entry_cnt ; ++i)
117
unsigned long res_ofs;
118
unsigned long res_siz;
119
unsigned short name_len;
121
/* read the entry header */
122
if (osfrb(fp, buf, 10))
125
/* parse the header */
126
res_ofs = t3rp4u(buf);
127
res_siz = t3rp4u(buf + 4);
128
name_len = osrp2(buf + 8);
130
/* read the entry's name */
131
if (name_len > sizeof(buf) || osfrb(fp, buf, name_len))
135
* if it matches the name we're looking for, note that we
138
if (name_len == resname_len
139
&& memicmp(resname, buf, name_len) == 0)
142
* note that we found it, and note its resource size
143
* and offset in the return structure - but keep
144
* scanning the rest of the directory, since we need
145
* to know where the directory ends to know where the
146
* actual resources begin
149
info->seek_pos = res_ofs;
155
* if we found our resource, the current seek position is the
156
* base of the offset we found in the directory; so fix up the
157
* offset to give the actual file location
161
/* fix up the offset with the actual file location */
162
info->seek_pos += osfpos(fp);
164
/* tell the caller we found it */
168
/* we didn't find it - seek to the end of this section */
169
osfseek(fp, endofs + startpos, OSFSK_SET);
171
else if (buf[0] == 4 && memcmp(buf+1, "$EOF", 4) == 0)
174
* that's the end of the file - we've finished without finding
175
* the resource, so return failure
182
* this isn't a section we're interested in - skip to the end
183
* of the section and keep going
185
osfseek(fp, endofs + startpos, OSFSK_SET);
190
/* ------------------------------------------------------------------------ */
192
* Find a resource in a T3 image file
194
static int t3_find_res(osfildef *fp, const char *resname,
200
/* note the length of the name we're seeking */
201
resname_len = strlen(resname);
204
* skip the file header - 11 bytes for the signature, 2 bytes for the
205
* format version, 32 reserved bytes, and 24 bytes for the timestamp
207
osfseek(fp, 11 + 2 + 32 + 24, OSFSK_CUR);
209
/* scan the data blocks */
214
/* read the block header */
215
if (osfrb(fp, buf, 10))
218
/* get the block size */
219
siz = t3rp4u(buf + 4);
222
if (memcmp(buf, "MRES", 4) == 0)
224
unsigned long base_ofs;
225
unsigned int entry_cnt;
229
* remember the current seek position - the data seek location
230
* for each index entry is given as an offset from this
233
base_ofs = osfpos(fp);
235
/* read the number of entries */
236
if (osfrb(fp, buf, 2))
239
/* parse the entry count */
240
entry_cnt = osrp2(buf);
242
/* read the entries */
243
for (i = 0 ; i < entry_cnt ; ++i)
245
unsigned long entry_ofs;
246
unsigned long entry_siz;
247
unsigned int entry_name_len;
251
/* read this index entry's header */
252
if (osfrb(fp, buf, 9))
255
/* parse the header */
256
entry_ofs = t3rp4u(buf);
257
entry_siz = t3rp4u(buf + 4);
258
entry_name_len = (unsigned char)buf[8];
260
/* read the entry's name */
261
if (osfrb(fp, buf, entry_name_len))
264
/* XOR the bytes of the name with 0xFF */
265
for (p = buf, rem = entry_name_len ; rem != 0 ; ++p, --rem)
268
/* if this is the one we're looking for, return it */
269
if (entry_name_len == resname_len
270
&& memicmp(resname, buf, resname_len) == 0)
273
* fill in the return information - note that the
274
* entry offset given in the header is an offset from
275
* data block's starting location, so fix this up to
276
* an absolute seek location for the return value
278
info->seek_pos = base_ofs + entry_ofs;
279
info->siz = entry_siz;
286
else if (memcmp(buf, "EOF ", 4) == 0)
289
* end of file - we've finished without finding the resource,
297
* we don't care about anything else - just skip this block
298
* and keep going; to skip the block, simply seek ahead by the
299
* size of the block's contents as given in the block header
301
osfseek(fp, siz, OSFSK_CUR);
306
/* ------------------------------------------------------------------------ */
308
* Find a multimedia resource with the given name in the given file. The
309
* file must be positioned at the start of the tads game file when we're
310
* invoked - this allows searching for a resource within a game file that
311
* is embedded in a larger file stream, since we don't care where within
312
* the osfildef stream the tads game file starts.
314
* Fills in the resource information structure with the seek offset and
315
* size of the resource in the file and returns true if the resource is
316
* found; returns false if a resource with the given name doesn't exist in
319
int tads_find_resource_fp(osfildef *fp, const char *resname,
324
/* read the signature */
325
if (!osfrb(fp, buf, 12))
327
/* seek back to the start of the header */
328
osfseek(fp, -12, OSFSK_CUR);
330
/* check which signature we have */
331
if (memcmp(buf, T2_SIG, strlen(T2_SIG)) == 0)
333
/* it's a tads 2 game file - read it accordingly */
334
return t2_find_res(fp, resname, info);
336
else if (memcmp(buf, T3_SIG, strlen(T3_SIG)) == 0)
338
/* it's a t3 image file - read it accordingly */
339
return t3_find_res(fp, resname, info);
344
* if we get here, it means either that we couldn't read the
345
* signature, or that we didn't recognize the signature - in either
346
* case, we can't parse the file at all, so we can't find the resource
352
* Find a resource in a file, given the filename.
354
* Fills in the resource information structure with the seek offset and
355
* size of the resource in the file and returns true if the resource is
356
* found; returns false if a resource with the given name doesn't exist in
359
int tads_find_resource(const char *fname, const char *resname,
366
if ((fp = osfoprb(fname, OSFTGAME)) == 0
367
&& (fp = osfoprb(fname, OSFTT3IMG)) == 0)
369
/* we couldn't open the file, so there's no resource to be found */
373
/* find the resource in the file */
374
found = tads_find_resource_fp(fp, resname, info);
376
/* we're done with the file - close it */
379
/* return our found or not-found indication */
383
/* ------------------------------------------------------------------------ */
385
* Testing main - looks for a given resource in a given file, and copies
386
* it to standard output if found.
390
int main(int argc, char **argv)
395
tads_resinfo res_info;
401
fprintf(stderr, "usage: resfind <filename> <resname>\n");
405
/* get the arguments */
410
if ((fp = osfoprb(fname, OSFTGAME)) == 0
411
&& (fp = osfoprb(fname, OSFTT3IMG)) == 0)
413
fprintf(stderr, "unable to open file \"%s\"\n", fname);
417
/* find the resource */
418
if (!tads_find_resource_fp(fp, resname, &res_info))
420
fprintf(stderr, "unable to find resource \"%s\"\n", resname);
425
/* seek to the resource */
426
osfseek(fp, res_info.seek_pos, OSFSK_SET);
428
/* copy the resource to standard output */
429
for (rem = res_info.siz ; rem != 0 ; )
435
* read up to the buffer size or up to the resource size
436
* remaining, whichever is smaller
443
if (osfrb(fp, buf, cur))
445
fprintf(stderr, "error reading %u bytes from file\n", cur);
450
/* copy the chunk to standard output */
451
fwrite(buf, cur, 1, stdout);
453
/* deduct the amount we just read from the size remaining */
457
/* done with the file - close it */