1/*	SCCS Id: @(#)dlb.c	3.4	1997/07/29	*/
2/* Copyright (c) Kenneth Lorber, Bethesda, Maryland, 1993. */
3/* NetHack may be freely redistributed.  See license for details. */
4
5#include "config.h"
6#include "dlb.h"
7
8#ifdef __DJGPP__
9#include <string.h>
10#endif
11
12#define DATAPREFIX 4
13
14#ifdef DLB
15/*
16 * Data librarian.  Present a STDIO-like interface to NetHack while
17 * multiplexing on one or more "data libraries".  If a file is not found
18 * in a given library, look for it outside the libraries.
19 */
20
21typedef struct dlb_procs {
22    boolean NDECL((*dlb_init_proc));
23    void NDECL((*dlb_cleanup_proc));
24    boolean FDECL((*dlb_fopen_proc), (DLB_P,const char *,const char *));
25    int FDECL((*dlb_fclose_proc), (DLB_P));
26    int FDECL((*dlb_fread_proc), (char *,int,int,DLB_P));
27    int FDECL((*dlb_fseek_proc), (DLB_P,long,int));
28    char *FDECL((*dlb_fgets_proc), (char *,int,DLB_P));
29    int FDECL((*dlb_fgetc_proc), (DLB_P));
30    long FDECL((*dlb_ftell_proc), (DLB_P));
31} dlb_procs_t;
32
33/* without extern.h via hack.h, these haven't been declared for us */
34extern FILE *FDECL(fopen_datafile, (const char *,const char *,int));
35
36#ifdef DLBLIB
37/*
38 * Library Implementation:
39 *
40 * When initialized, we open all library files and read in their tables
41 * of contents.  The library files stay open all the time.  When
42 * a open is requested, the libraries' directories are searched.  If
43 * successful, we return a descriptor that contains the library, file
44 * size, and current file mark.  This descriptor is used for all
45 * successive calls.
46 *
47 * The ability to open more than one library is supported but used
48 * only in the Amiga port (the second library holds the sound files).
49 * For Unix, the idea would be to split the NetHack library
50 * into text and binary parts, where the text version could be shared.
51 */
52
53#define MAX_LIBS 4
54static library dlb_libs[MAX_LIBS];
55
56static boolean FDECL(readlibdir,(library *lp));
57static boolean FDECL(find_file,(const char *name, library **lib, long *startp,
58								long *sizep));
59static boolean NDECL(lib_dlb_init);
60static void NDECL(lib_dlb_cleanup);
61static boolean FDECL(lib_dlb_fopen,(dlb *, const char *, const char *));
62static int FDECL(lib_dlb_fclose,(dlb *));
63static int FDECL(lib_dlb_fread,(char *, int, int, dlb *));
64static int FDECL(lib_dlb_fseek,(dlb *, long, int));
65static char *FDECL(lib_dlb_fgets,(char *, int, dlb *));
66static int FDECL(lib_dlb_fgetc,(dlb *));
67static long FDECL(lib_dlb_ftell,(dlb *));
68
69/* not static because shared with dlb_main.c */
70boolean FDECL(open_library,(const char *lib_name, library *lp));
71void FDECL(close_library,(library *lp));
72
73/* without extern.h via hack.h, these haven't been declared for us */
74extern char *FDECL(eos, (char *));
75
76
77
78/*
79 * Read the directory out of the library.  Return 1 if successful,
80 * 0 if it failed.
81 *
82 * NOTE: An improvement of the file structure should be the file
83 * size as part of the directory entry or perhaps in place of the
84 * offset -- the offset can be calculated by a running tally of
85 * the sizes.
86 *
87 * Library file structure:
88 *
89 * HEADER:
90 * %3ld	library FORMAT revision (currently rev 1)
91 * %1c	space
92 * %8ld	# of files in archive (includes 1 for directory)
93 * %1c	space
94 * %8ld	size of allocation for string space for directory names
95 * %1c	space
96 * %8ld	library offset - sanity check - lseek target for start of first file
97 * %1c	space
98 * %8ld	size - sanity check - byte size of complete archive file
99 *
100 * followed by one DIRECTORY entry for each file in the archive, including
101 *  the directory itself:
102 * %1c	handling information (compression, etc.)  Always ' ' in rev 1.
103 * %s	file name
104 * %1c	space
105 * %8ld	offset in archive file of start of this file
106 * %c	newline
107 *
108 * followed by the contents of the files
109 */
110#define DLB_MIN_VERS  1	/* min library version readable by this code */
111#define DLB_MAX_VERS  1	/* max library version readable by this code */
112
113/*
114 * Read the directory from the library file.   This will allocate and
115 * fill in our globals.  The file pointer is reset back to position
116 * zero.  If any part fails, leave nothing that needs to be deallocated.
117 *
118 * Return TRUE on success, FALSE on failure.
119 */
120static boolean
121readlibdir(lp)
122    library *lp;	/* library pointer to fill in */
123{
124    int i;
125    char *sp;
126    long liboffset, totalsize;
127
128    if (fscanf(lp->fdata, "%ld %ld %ld %ld %ld\n",
129	    &lp->rev,&lp->nentries,&lp->strsize,&liboffset,&totalsize) != 5)
130	return FALSE;
131    if (lp->rev > DLB_MAX_VERS || lp->rev < DLB_MIN_VERS) return FALSE;
132
133    lp->dir = (libdir *) alloc(lp->nentries * sizeof(libdir));
134    lp->sspace = (char *) alloc(lp->strsize);
135
136    /* read in each directory entry */
137    for (i = 0, sp = lp->sspace; i < lp->nentries; i++) {
138	lp->dir[i].fname = sp;
139	if (fscanf(lp->fdata, "%c%s %ld\n",
140			&lp->dir[i].handling, sp, &lp->dir[i].foffset) != 3) {
141	    free((genericptr_t) lp->dir);
142	    free((genericptr_t) lp->sspace);
143	    lp->dir = (libdir *) 0;
144	    lp->sspace = (char *) 0;
145	    return FALSE;
146	}
147	sp = eos(sp) + 1;
148    }
149
150    /* calculate file sizes using offset information */
151    for (i = 0; i < lp->nentries; i++) {
152	if (i == lp->nentries - 1)
153	    lp->dir[i].fsize = totalsize - lp->dir[i].foffset;
154	else
155	    lp->dir[i].fsize = lp->dir[i+1].foffset - lp->dir[i].foffset;
156    }
157
158    (void) fseek(lp->fdata, 0L, SEEK_SET);	/* reset back to zero */
159    lp->fmark = 0;
160
161    return TRUE;
162}
163
164/*
165 * Look for the file in our directory structure.  Return 1 if successful,
166 * 0 if not found.  Fill in the size and starting position.
167 */
168static boolean
169find_file(name, lib, startp, sizep)
170    const char *name;
171    library **lib;
172    long *startp, *sizep;
173{
174    int i, j;
175    library *lp;
176
177    for (i = 0; i < MAX_LIBS && dlb_libs[i].fdata; i++) {
178	lp = &dlb_libs[i];
179	for (j = 0; j < lp->nentries; j++) {
180	    if (FILENAME_CMP(name, lp->dir[j].fname) == 0) {
181		*lib = lp;
182		*startp = lp->dir[j].foffset;
183		*sizep = lp->dir[j].fsize;
184		return TRUE;
185	    }
186	}
187    }
188    *lib = (library *) 0;
189    *startp = *sizep = 0;
190    return FALSE;
191}
192
193/*
194 * Open the library of the given name and fill in the given library
195 * structure.  Return TRUE if successful, FALSE otherwise.
196 */
197boolean
198open_library(lib_name, lp)
199    const char *lib_name;
200    library *lp;
201{
202    boolean status = FALSE;
203
204    lp->fdata = fopen_datafile(lib_name, RDBMODE, DATAPREFIX);
205    if (lp->fdata) {
206	if (readlibdir(lp)) {
207	    status = TRUE;
208	} else {
209	    (void) fclose(lp->fdata);
210	    lp->fdata = (FILE *) 0;
211	}
212    }
213    return status;
214}
215
216void
217close_library(lp)
218    library *lp;
219{
220    (void) fclose(lp->fdata);
221    free((genericptr_t) lp->dir);
222    free((genericptr_t) lp->sspace);
223
224    (void) memset((char *)lp, 0, sizeof(library));
225}
226
227/*
228 * Open the library file once using stdio.  Keep it open, but
229 * keep track of the file position.
230 */
231static boolean
232lib_dlb_init()
233{
234    /* zero out array */
235    (void) memset((char *)&dlb_libs[0], 0, sizeof(dlb_libs));
236
237    /* To open more than one library, add open library calls here. */
238    if (!open_library(DLBFILE, &dlb_libs[0])) return FALSE;
239#ifdef DLBFILE2
240    if (!open_library(DLBFILE2, &dlb_libs[1]))  {
241	close_library(&dlb_libs[0]);
242	return FALSE;
243    }
244#endif
245    return TRUE;
246}
247
248static void
249lib_dlb_cleanup()
250{
251    int i;
252
253    /* close the data file(s) */
254    for (i = 0; i < MAX_LIBS && dlb_libs[i].fdata; i++)
255	close_library(&dlb_libs[i]);
256}
257
258static boolean
259lib_dlb_fopen(dp, name, mode)
260    dlb *dp;
261    const char *name, *mode;
262{
263    long start, size;
264    library *lp;
265
266    /* look up file in directory */
267    if (find_file(name, &lp, &start, &size)) {
268	dp->lib = lp;
269	dp->start = start;
270	dp->size = size;
271	dp->mark = 0;
272	return TRUE;
273	}
274
275    return FALSE;	/* failed */
276}
277
278static int
279lib_dlb_fclose(dp)
280    dlb *dp;
281{
282    /* nothing needs to be done */
283    return 0;
284}
285
286static int
287lib_dlb_fread(buf, size, quan, dp)
288    char *buf;
289    int size, quan;
290    dlb *dp;
291{
292    long pos, nread, nbytes;
293
294    /* make sure we don't read into the next file */
295    if ((dp->size - dp->mark) < (size * quan))
296	quan = (dp->size - dp->mark) / size;
297    if (quan == 0) return 0;
298
299    pos = dp->start + dp->mark;
300    if (dp->lib->fmark != pos) {
301	fseek(dp->lib->fdata, pos, SEEK_SET);	/* check for error??? */
302	dp->lib->fmark = pos;
303    }
304
305    nread = fread(buf, size, quan, dp->lib->fdata);
306    nbytes = nread * size;
307    dp->mark += nbytes;
308    dp->lib->fmark += nbytes;
309
310    return nread;
311}
312
313static int
314lib_dlb_fseek(dp, pos, whence)
315    dlb *dp;
316    long pos;
317    int whence;
318{
319    long curpos;
320
321    switch (whence) {
322	case SEEK_CUR:	   curpos = dp->mark + pos;	break;
323	case SEEK_END:	   curpos = dp->size - pos;	break;
324	default: /* set */ curpos = pos;		break;
325    }
326    if (curpos < 0) curpos = 0;
327    if (curpos > dp->size) curpos = dp->size;
328
329    dp->mark = curpos;
330    return 0;
331}
332
333static char *
334lib_dlb_fgets(buf, len, dp)
335    char *buf;
336    int len;
337    dlb *dp;
338{
339    int i;
340    char *bp, c = 0;
341
342    if (len <= 0) return buf;	/* sanity check */
343
344    /* return NULL on EOF */
345    if (dp->mark >= dp->size) return (char *) 0;
346
347    len--;	/* save room for null */
348    for (i = 0, bp = buf;
349		i < len && dp->mark < dp->size && c != '\n'; i++, bp++) {
350	if (dlb_fread(bp, 1, 1, dp) <= 0) break;	/* EOF or error */
351	c = *bp;
352    }
353    *bp = '\0';
354
355#if defined(MSDOS) || defined(WIN32)
356    if ((bp = index(buf, '\r')) != 0) {
357	*bp++ = '\n';
358	*bp = '\0';
359    }
360#endif
361
362    return buf;
363}
364
365static int
366lib_dlb_fgetc(dp)
367    dlb *dp;
368{
369    char c;
370
371    if (lib_dlb_fread(&c, 1, 1, dp) != 1) return EOF;
372    return (int) c;
373}
374
375
376static long
377lib_dlb_ftell(dp)
378    dlb *dp;
379{
380    return dp->mark;
381}
382
383const dlb_procs_t lib_dlb_procs = {
384    lib_dlb_init,
385    lib_dlb_cleanup,
386    lib_dlb_fopen,
387    lib_dlb_fclose,
388    lib_dlb_fread,
389    lib_dlb_fseek,
390    lib_dlb_fgets,
391    lib_dlb_fgetc,
392    lib_dlb_ftell
393};
394
395#endif /* DLBLIB */
396
397#ifdef DLBRSRC
398const dlb_procs_t rsrc_dlb_procs = {
399    rsrc_dlb_init,
400    rsrc_dlb_cleanup,
401    rsrc_dlb_fopen,
402    rsrc_dlb_fclose,
403    rsrc_dlb_fread,
404    rsrc_dlb_fseek,
405    rsrc_dlb_fgets,
406    rsrc_dlb_fgetc,
407    rsrc_dlb_ftell
408};
409#endif
410
411/* Global wrapper functions ------------------------------------------------ */
412
413#define do_dlb_init (*dlb_procs->dlb_init_proc)
414#define do_dlb_cleanup (*dlb_procs->dlb_cleanup_proc)
415#define do_dlb_fopen (*dlb_procs->dlb_fopen_proc)
416#define do_dlb_fclose (*dlb_procs->dlb_fclose_proc)
417#define do_dlb_fread (*dlb_procs->dlb_fread_proc)
418#define do_dlb_fseek (*dlb_procs->dlb_fseek_proc)
419#define do_dlb_fgets (*dlb_procs->dlb_fgets_proc)
420#define do_dlb_fgetc (*dlb_procs->dlb_fgetc_proc)
421#define do_dlb_ftell (*dlb_procs->dlb_ftell_proc)
422
423static const dlb_procs_t *dlb_procs;
424static boolean dlb_initialized = FALSE;
425
426boolean
427dlb_init()
428{
429    if (!dlb_initialized) {
430#ifdef DLBLIB
431	dlb_procs = &lib_dlb_procs;
432#endif
433#ifdef DLBRSRC
434	dlb_procs = &rsrc_dlb_procs;
435#endif
436
437	if (dlb_procs)
438	    dlb_initialized = do_dlb_init();
439    }
440
441    return dlb_initialized;
442}
443
444void
445dlb_cleanup()
446{
447    if (dlb_initialized) {
448	do_dlb_cleanup();
449	dlb_initialized = FALSE;
450    }
451}
452
453dlb *
454dlb_fopen(name, mode)
455    const char *name, *mode;
456{
457    FILE *fp;
458    dlb *dp;
459
460    if (!dlb_initialized) return (dlb *) 0;
461
462    dp = (dlb *) alloc(sizeof(dlb));
463    if (do_dlb_fopen(dp, name, mode))
464    	dp->fp = (FILE *) 0;
465    else if ((fp = fopen_datafile(name, mode, DATAPREFIX)) != 0)
466	dp->fp = fp;
467    else {
468	/* can't find anything */
469	free((genericptr_t) dp);
470	dp = (dlb *) 0;
471	}
472
473    return dp;
474}
475
476int
477dlb_fclose(dp)
478    dlb *dp;
479{
480	int ret = 0;
481
482    if (dlb_initialized) {
483	if (dp->fp) ret = fclose(dp->fp);
484	else ret = do_dlb_fclose(dp);
485
486	free((genericptr_t) dp);
487    }
488    return ret;
489}
490
491int
492dlb_fread(buf, size, quan, dp)
493    char *buf;
494    int size, quan;
495    dlb *dp;
496{
497    if (!dlb_initialized || size <= 0 || quan <= 0) return 0;
498    if (dp->fp) return (int) fread(buf, size, quan, dp->fp);
499    return do_dlb_fread(buf, size, quan, dp);
500}
501
502int
503dlb_fseek(dp, pos, whence)
504    dlb *dp;
505    long pos;
506    int whence;
507{
508    if (!dlb_initialized) return EOF;
509    if (dp->fp) return fseek(dp->fp, pos, whence);
510    return do_dlb_fseek(dp, pos, whence);
511}
512
513char *
514dlb_fgets(buf, len, dp)
515    char *buf;
516    int len;
517    dlb *dp;
518{
519    if (!dlb_initialized) return (char *) 0;
520    if (dp->fp) return fgets(buf, len, dp->fp);
521    return do_dlb_fgets(buf, len, dp);
522}
523
524int
525dlb_fgetc(dp)
526    dlb *dp;
527{
528    if (!dlb_initialized) return EOF;
529    if (dp->fp) return fgetc(dp->fp);
530    return do_dlb_fgetc(dp);
531}
532
533long
534dlb_ftell(dp)
535    dlb *dp;
536{
537    if (!dlb_initialized) return 0;
538    if (dp->fp) return ftell(dp->fp);
539    return do_dlb_ftell(dp);
540}
541
542#endif /* DLB */
543
544/*dlb.c*/
545