frotz
dfrotz
*.o
+*.O
+*.OBJ
+*.BAK
+*.SWP
+*.DSK
*.a
- defines.h
- /tags
- /tmp
+ src/*/defines.h
+ src/common/git_hash.h
-Summary of changes between Unix Frotz 2.43 and Unix Frotz 2.44:
-===============================================================
+Summary of changes between Frotz 2.44 and Frotz 2.45:
+=====================================================
-Unix Frotz 2.43 was released on (date)
+Frotz 2.45 was released on ????
- NEW FEATURE
+ NEW FEATURES
-- Game files can be loaded from Blorb files.
-
-- Sound effects are now loaded from Blorb files.
+- Added -R option for restoring save file directly from the command line.
-ENHANCEMENTS
+ - Sound effects in AIFF, MOD, and OGG formats supported per the Blorb specs.
+
++- Sound driver selection is automated through the use of libao.
++
+
+Summary of changes between Frotz 2.43 and Frotz 2.44:
+=====================================================
+
+Frotz 2.44 was released on Sunday May 17, 2015.
+
+NEW FEATURES
+
+- Blorb support implemented. No support for audio in Blorb files yet.
+
+- Added an -m option for Dumb Frotz to suppress the [MORE] prompt.
+
+- Makefile and code for compiling for 16-bit DOS.
+
+ - DOS Frotz now distributed in the same source file as Unix Frotz.
+
-- Sound driver selection is automated through the use of libao.
+BUG FIXES
-- Makefile cleaned up.
+- Fixed a problem with saving and restoring in certain games.
-- Documentation updated.
+- Added some fflush() calls to make Dumb Frotz more suitable for bot use.
+
+- Removed old-style save support. It was getting hard to manage. Only
+ the Quetzal save file format is supported now.
+
+- Fixed assorted minor 64-bitness problems.
+
++- Makefile cleaned up.
+
Summary of changes between Unix Frotz 2.42 and Unix Frotz 2.43:
===============================================================
#LIB = -L/usr/pkg/lib
#LIB = -L/usr/freeware/lib
#LIB = -L/5usr/lib
- #LIB = -L/path/to/libncurses.so
-
- # One of these must always be uncommented. If your vendor-supplied
- # curses library won't work, comment out the first option and uncomment
- # the second.
- #
- CURSES = -lcurses
- #CURSES = -lncurses
-
- # Uncomment this if your need to use ncurses instead of the
- # vendor-supplied curses library. This just tells the compile process
- # which header to include, so don't worry if ncurses is all you have
- # (like on Linux). You'll be fine.
- #
- #CURSES_DEF = -DUSE_NCURSES_H
+# Uncomment this if you're compiling Unix Frotz on a machine that lacks
+# the strrchr() libc library call. If you don't know what this means,
+# leave it alone.
+#
+#STRRCHR_DEF = -DNO_STRRCHR
+
# Uncomment this if you're compiling Unix Frotz on a machine that lacks
# the memmove(3) system call. If you don't know what this means, leave it
# alone.
.SUFFIXES: .c .o .h
$(COMMON_OBJECT): %.o: %.c
- $(CC) $(CFLAGS) $(OPTS) $(COMMON_DEFS) -o $@ -c $<
+ $(CC) $(OPTS) -o $@ -c $<
$(BLORB_OBJECT): %.o: %.c
- $(CC) $(OPTS) -o $@ -c $<
+ $(CC) $(CFLAGS) $(OPTS) -o $@ -c $<
$(DUMB_OBJECT): %.o: %.c
- $(CC) $(OPTS) -o $@ -c $<
+ $(CC) $(CFLAGS) $(OPTS) -o $@ -c $<
$(CURSES_OBJECT): %.o: %.c
- $(CC) $(CFLAGS) $(OPTS) $(CURSES_DEFS) $(INCL) -o $@ -c $<
+ $(CC) $(OPTS) -o $@ -c $<
+
+
+ ####################################
+ # Get the defines set up just right
+ #
+ $(COMMON_DIR)/defines.h:
+ @echo "Generating $@"
+ @echo "#define VERSION \"$(VERSION)\"" > $@
- $(SDL_OBJECT): %.o: %.c
- $(CC) $(CFLAGS) $(OPTS) $(SDL_DEFS) $(INCL) -o $@ -c $<
+ $(CURSES_DIR)/defines.h:
+ @echo "Generating $@"
+ @echo "#define CONFIG_DIR \"$(CONFIG_DIR)\"" >> $@
+ @echo "#define SOUND \"$(SOUND)\"" >> $@
+ @echo "#define SAMPLERATE $(SAMPLERATE)" >> $@
+ @echo "#define BUFFSIZE $(BUFFSIZE)" >> $@
+ @echo "#define DEFAULT_CONVERTER $(DEFAULT_CONVERTER)" >> $@
+ ifeq ($(SOUND), none)
+ @echo "#define NO_SOUND" >> $@
+ endif
+ ifndef SOUND
+ @echo "#define NO_SOUND" >> $@
+ endif
+
+ ifdef COLOR
+ @echo "#define COLOR_SUPPORT" >> $@
+ endif
+
+ ifdef NO_MEMMOVE
+ @echo "#define NO_MEMMOVE" >> $@
+ endif
+
+
+ ########################################################################
# If you're going to make this target manually, you'd better know which
# config target to make first.
#
$(COMMON_TARGET): $(COMMON_OBJECT)
@echo
@echo "Archiving common code..."
- ar rc $(COMMON_TARGET) $(COMMON_OBJECT)
- ranlib $(COMMON_TARGET)
+ $(AR) rc $(COMMON_TARGET) $(COMMON_OBJECT)
+ $(RANLIB) $(COMMON_TARGET)
@echo
- curses_lib: config_curses $(CURSES_TARGET)
+ curses_lib: $(CURSES_TARGET)
$(CURSES_TARGET): $(CURSES_OBJECT)
@echo
@echo "Archiving curses interface code..."
$(DUMB_TARGET): $(DUMB_OBJECT)
@echo
@echo "Archiving dumb interface code..."
- ar rc $(DUMB_TARGET) $(DUMB_OBJECT)
- ranlib $(DUMB_TARGET)
+ $(AR) rc $(DUMB_TARGET) $(DUMB_OBJECT)
+ $(RANLIB) $(DUMB_TARGET)
@echo
- sdl_lib: $(SDL_TARGET)
- $(SDL_TARGET): $(SDL_OBJECT)
- @echo
- @echo "Archiving SDL interface code..."
- $(AR) rc $(SDL_TARGET) $(SDL_OBJECT)
- $(RANLIB) $(SDL_TARGET)
- @echo
-
blorb_lib: $(BLORB_TARGET)
$(BLORB_TARGET): $(BLORB_OBJECT)
@echo
@echo "Archiving Blorb file handling code..."
- ar rc $(BLORB_TARGET) $(BLORB_OBJECT)
- ranlib $(BLORB_TARGET)
+ $(AR) rc $(BLORB_TARGET) $(BLORB_OBJECT)
+ $(RANLIB) $(BLORB_TARGET)
@echo
-
- soundcard.h:
- @if [ ! -f $(SRCDIR)/soundcard.h ] ; then \
- sh $(SRCDIR)/misc/findsound.sh $(SRCDIR); \
- fi
-
install: $(NAME)
- strip $(BINNAME)$(EXTENSION)
- install -d $(PREFIX)/bin
- install -d $(MAN_PREFIX)/man/man6
- install -c -m 755 $(BINNAME)$(EXTENSION) $(PREFIX)/bin
- install -c -m 644 doc/$(NAME).6 $(MAN_PREFIX)/man/man6
+ @install -D -m 755 $(BINNAME)$(EXTENSION) "$(DESTDIR)$(PREFIX)/bin/$(BINNAME)$(EXTENSION)"
+ @install -D -m 644 doc/$(NAME).6 "$(DESTDIR)$(MAN_PREFIX)/man/man6/$(NAME).6"
uninstall:
- rm -f $(PREFIX)/bin/$(NAME)
- rm -f $(MAN_PREFIX)/man/man6/$(NAME).6
+ @rm -f "$(DESTDIR)$(PREFIX)/bin/$(NAME)"
+ @rm -f "$(DESTDIR)$(MAN_PREFIX)/man/man6/$(NAME).6"
- deinstall: uninstall
-
install_dumb: d$(NAME)
- strip d$(BINNAME)$(EXTENSION)
- install -d $(PREFIX)/bin
- install -d $(MAN_PREFIX)/man/man6
- install -c -m 755 d$(BINNAME)$(EXTENSION) $(PREFIX)/bin
- install -c -m 644 doc/d$(NAME).6 $(MAN_PREFIX)/man/man6
+ @install -D -m 755 d$(BINNAME)$(EXTENSION) "$(DESTDIR)$(PREFIX)/bin/d$(BINNAME)$(EXTENSION)"
+ @install -D -m 644 doc/d$(NAME).6 "$(DESTDIR)$(MAN_PREFIX)/man/man6/d$(NAME).6"
uninstall_dumb:
- rm -f $(PREFIX)/bin/d$(NAME)
- rm -f $(MAN_PREFIX)/man/man6/d$(NAME).6
+ @rm -f "$(DESTDIR)$(PREFIX)/bin/d$(NAME)"
+ @rm -f "$(DESTDIR)$(MAN_PREFIX)/man/man6/d$(NAME).6"
- deinstall_dumb: uninstall_dumb
-
- distro: dist
-
- dist: distclean
+ dist: distclean hash
mkdir $(distdir)
@for file in `ls`; do \
if test $$file != $(distdir); then \
- FROTZ V2.44 - An interpreter for all Infocom and other Z-machine games.
-FROTZ V2.45 - An interpreter for all Infocom and other Z-machine games.
++FROTZ V2.45pre - An interpreter for all Infocom and other Z-machine games.
Complies with standard 1.0 of Graham Nelson's specification.
Originally written by Stefan Jokisch in 1995-1997.
-I have mixing sortof working. Still to be done is to convert the sample
-rate of the AIFF data to 44100, which is what is produced in playmod()
-and playogg(). Another concern is how I should take care of a situation
-in which playogg() or playmod() ends before the AIFF sample does.
-Libsamplerate appears to be a good library for doing this, but I don't
-know yet how to use it.
-
-
-
+ Redo how the core and interfaces use the f_setup structure. Failure to
+ do this properly led to a segfault when dumb frotz saves a game.
+ Look at using a memset() or calloc() there.
+
+
Rework os_process_arguments() in src/curses/ux_init.c to make Frotz more
convenient to use as a BBS door or MUD module.
.\" -*- nroff -*-
- .TH DFROTZ 6 2.44
-.TH DFROTZ 6 2.45
++.TH DFROTZ 6 2.45pre
.SH NAME
dfrotz \- interpreter for Infocom and other Z-Machine games (dumb interface)
.\" -*- nroff -*-
- .TH FROTZ 6 2.44
-.TH FROTZ 6 2.45
++.TH FROTZ 6 2.45pre
.SH NAME
frotz \- interpreter for Infocom and other Z-Machine games
* Close the story file and deallocate memory.
*
*/
-
void reset_memory (void)
{
- if (story_fp)
+ if (story_fp != NULL)
fclose (story_fp);
story_fp = NULL;
--- /dev/null
- int i;
+ /*
+ * ux_audio.c - Unix interface, sound support
+ *
+ * This file is part of Frotz.
+ *
+ * Frotz 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.
+ *
+ * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+ #define __UNIX_PORT_FILE
+
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ #include <pthread.h>
+ #include <semaphore.h>
+ #include <signal.h>
+
+ #ifdef USE_NCURSES_H
+ #include <ncurses.h>
+ #else
+ #include <curses.h>
+ #endif
+
+ #include "ux_frotz.h"
+ #include "ux_locks.h"
+
+ #ifndef NO_SOUND
+
+ #include <ao/ao.h>
+ #include <sndfile.h>
+ #include <samplerate.h>
+ #include <vorbis/codec.h>
+ #include <vorbis/vorbisfile.h>
+ #include <libmodplug/modplug.h>
+
+ #define MAX(x,y) ((x)>(y)) ? (x) : (y)
+ #define MIN(x,y) ((x)<(y)) ? (x) : (y)
+
+ enum sound_type {
+ FORM,
+ OGGV,
+ MOD
+ };
+
+ typedef struct {
+ FILE *fp;
+ bb_result_t result;
+ enum sound_type type;
+ int number;
+ int vol;
+ int repeats;
+ } EFFECT;
+
+ static void *playaiff(EFFECT *);
+ static void *playmusic(EFFECT *);
+ static void *playmod(EFFECT *);
+ static void *playogg(EFFECT *);
+
+ static void floattopcm16(short *, float *, int);
+ static void pcm16tofloat(float *, short *, int);
+ static void stereoize(float *, float *, size_t);
+
+ static int mypower(int, int);
+ static char *getfiledata(FILE *, long *);
+ static void *mixer(void *);
+
+ static pthread_t mixer_id;
+ static pthread_t playaiff_id;
+ static pthread_t playmusic_id;
+ static pthread_mutex_t mutex;
+ static sem_t playaiff_okay;
+ static sem_t playmusic_okay;
+
+ bool bleep_playing = FALSE;
+ bool bleep_stop = FALSE;
+
+ int bleepcount;
+ int bleepnum;
+
+ bool music_playing = FALSE;
+ bool music_stop = FALSE;
+
+ typedef struct
+ {
+ sem_t full;
+ sem_t empty;
+ float *samples;
+ int nsamples;
+ } audiobuffer;
+
+ audiobuffer bleep_buffer;
+ audiobuffer music_buffer;
+
+ void audiobuffer_init(audiobuffer *ab)
+ {
+ sem_init(&ab->full, 0, 0);
+ sem_init(&ab->empty, 0, 0);
+ sem_post(&ab->empty);
+ ab->samples = malloc(BUFFSIZE * 2 * sizeof(float));
+ ab->nsamples = 0;
+ }
+
+
+ /*
+ * os_init_sound
+ *
+ * Do any required setup for sound output.
+ * Here we start a thread to act as a mixer.
+ *
+ */
+ void os_init_sound(void)
+ {
+ int err;
+ static pthread_attr_t attr;
+
+ pthread_mutex_init(&mutex, NULL);
+ audiobuffer_init(&music_buffer);
+ audiobuffer_init(&bleep_buffer);
+ sem_init(&playaiff_okay, 0, 0);
+ sem_init(&playmusic_okay, 0, 0);
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ err = pthread_create(&(mixer_id), &attr, (void *) &mixer, NULL);
+ if (err != 0) {
+ printf("Can't create mixer thread :[%s]", strerror(err));
+ exit(1);
+ }
+ }
+
+
+ /*
+ * os_beep
+ *
+ * Play a beep sound. Ideally, the sound should be high- (number == 1)
+ * or low-pitched (number == 2).
+ *
+ */
+ void os_beep (int number)
+ {
+ int i = number;
+ i++;
+
+ beep();
+ }/* os_beep */
+
+
+ /*
+ * os_prepare_sample
+ *
+ * Load the sample from the disk.
+ *
+ * Actually it's more efficient these days to load and play a sound in
+ * the same operation. This function therefore does nothing.
+ *
+ */
+ void os_prepare_sample (int number)
+ {
+ int i = number;
+ i++;
+
+ return;
+ }/* os_prepare_sample */
+
+
+ /*
+ * os_start_sample
+ *
+ * Play the given sample at the given volume (ranging from 1 to 8 and
+ * 255 meaning a default volume). The sound is played once or several
+ * times in the background (255 meaning forever). In Z-code 3 the
+ * repeats value is always 0 and the number of repeats is taken from
+ * the sound file itself. The end_of_sound function is called as soon
+ * as the sound finishes.
+ *
+ */
+ void os_start_sample (int number, int volume, int repeats, zword eos)
+ {
+ bb_result_t resource;
+ EFFECT myeffect;
+ int err;
+ static pthread_attr_t attr;
+ zword foo = eos;
+
+ foo++;
+
+ if (blorb_map == NULL) return;
+
+ if (bb_err_None != bb_load_resource(blorb_map, bb_method_FilePos, &resource, bb_ID_Snd, number))
+ return;
+
+ myeffect.fp = blorb_fp;
+ myeffect.result = resource;
+ myeffect.vol = volume;
+ myeffect.repeats = repeats;
+ myeffect.number = number;
+
+ pthread_attr_init(&attr);
+
+ if (blorb_map->chunks[resource.chunknum].type == bb_make_id('F','O','R','M'))
+ myeffect.type = FORM;
+ else if (blorb_map->chunks[resource.chunknum].type == bb_make_id('M','O','D',' '))
+ myeffect.type = MOD;
+ else if (blorb_map->chunks[resource.chunknum].type == bb_make_id('O','G','G','V'))
+ myeffect.type = OGGV;
+
+ if (myeffect.type == FORM) {
+ if (bleep_playing) {
+ bleep_playing = FALSE;
+ pthread_join(playaiff_id, NULL);
+ }
+ err = pthread_create(&playaiff_id, &attr, (void *) &playaiff, &myeffect);
+ if (err != 0) {
+ printf("Can't create playaiff thread :[%s]", strerror(err));
+ return;
+ }
+ sem_wait(&playaiff_okay);
+ } else if (myeffect.type == MOD || myeffect.type == OGGV) {
+ if (music_playing) {
+ music_playing = FALSE;
+ pthread_join(playmusic_id, NULL);
+ }
+ err = pthread_create(&playmusic_id, &attr, (void *) &playmusic, &myeffect);
+ if (err != 0) {
+ printf("Can't create playmusic thread :[%s]", strerror(err));
+ return;
+ }
+ sem_wait(&playmusic_okay);
+ } else {
+ /* Something else was presented as an audio chunk. Ignore it. */
+ }
+ }/* os_start_sample */
+
+
+ /*
+ * os_stop_sample
+ *
+ * Turn off the current sample.
+ *
+ */
+ void os_stop_sample (int number)
+ {
+ if (bleep_playing && (number == bleepnum || number == 0)) {
+ bleep_playing = FALSE;
+ sem_post(&bleep_buffer.empty);
+ pthread_join(playaiff_id, 0);
+ }
+
+ if (get_music_playing() && (number == get_musicnum () || number == 0)) {
+ set_music_playing(false);
+ sem_post(&music_buffer.empty);
+ pthread_join(playmusic_id, 0);
+ }
+
+ return;
+ }/* os_stop_sample */
+
+
+ /*
+ * os_finish_with_sample
+ *
+ * Remove the current sample from memory (if any).
+ *
+ */
+ void os_finish_with_sample (int number)
+ {
+ os_stop_sample(number);
+
+ }/* os_finish_with_sample */
+
+
+ /*
+ * os_wait_sample
+ *
+ * Stop repeating the current sample and wait until it finishes.
+ *
+ */
+ void os_wait_sample (void)
+ {
+
+ /* Not implemented */
+
+ }/* os_wait_sample */
+
+
+ /*
+ **********************************************
+ * These functions are internal to ux_audio.c
+ *
+ **********************************************
+ */
+
+ /*
+ * mixer
+ *
+ * In a classic producer/consumer arrangement, this mixer watches for audio
+ * data to be placed in *bleepbuffer or *musicbuffer. When a semaphore for
+ * either is raised, the mixer processes the buffer.
+ *
+ * Data presented to the mixer must be floats at 44100hz
+ *
+ */
+ static void *mixer(void * UNUSED(arg))
+ {
+ short *shortbuffer;
+ int default_driver;
+ ao_device *device;
+ ao_sample_format format;
+ int samplecount;
+
+ ao_initialize();
+ default_driver = ao_default_driver_id();
+
+ shortbuffer = malloc(BUFFSIZE * sizeof(short) * 2);
+ if (shortbuffer == NULL) {
+ printf("Unable to malloc shortbuffer\n");
+ exit(1);
+ }
+
+ memset(&format, 0, sizeof(ao_sample_format));
+
+ format.byte_format = AO_FMT_NATIVE;
+ format.bits = 16;
+ format.channels = 2;
+ format.rate = SAMPLERATE;
+
+ device = NULL;
+
+ while (1) {
+ if(music_playing) {
+ sem_wait(&music_buffer.full); /* Wait until output buffer is full */
+ }
+ if(bleep_playing ) {
+ sem_wait(&bleep_buffer.full); /* Wait until output buffer is full */
+ }
+
+ pthread_mutex_lock(&mutex); /* Acquire mutex */
+
+ if (device == NULL) {
+ device = ao_open_live(default_driver, &format, NULL);
+ if (device == NULL) {
+ printf(" Error opening sound device.\n");
+ }
+ }
+
+ if (bleep_playing && !music_playing) {
+ floattopcm16(shortbuffer, bleep_buffer.samples, bleep_buffer.nsamples);
+ ao_play(device, (char *) shortbuffer, bleep_buffer.nsamples * sizeof(short));
+ bleep_buffer.nsamples = 0;
+ }
+
+ if (music_playing && !bleep_playing) {
+ floattopcm16(shortbuffer, music_buffer.samples, music_buffer.nsamples);
+ ao_play(device, (char *) shortbuffer, music_buffer.nsamples * sizeof(short));
+ music_buffer.nsamples = 0;
+ }
+
+ if (music_playing && bleep_playing) {
+ int samples = 100000;
+ if(bleep_buffer.nsamples == -1)
+ bleep_buffer.nsamples = 0;
+ if(music_buffer.nsamples == -1)
+ music_buffer.nsamples = 0;
+ if(samples > bleep_buffer.nsamples && bleep_buffer.nsamples > 0)
+ samples = bleep_buffer.nsamples;
+
+ if(samples > music_buffer.nsamples && music_buffer.nsamples > 0)
+ samples = music_buffer.nsamples;
+
+ //both buffers have invalid sample data or are empty
+ if(samples == 100000)
+ samples = 0;
+
+ float *outbuf = calloc(samples+1,sizeof(float));
+ for(int i=0; i < samples; ++i)
+ outbuf[i] += music_buffer.samples[i];
+ for(int i=0; i < samples; ++i)
+ outbuf[i] += bleep_buffer.samples[i];
+
+ //only partially consume data
+ if(bleep_buffer.nsamples > samples) {
+ memmove(bleep_buffer.samples, bleep_buffer.samples+samples,
+ sizeof(float)*(bleep_buffer.nsamples-samples));
+ }
+ if(bleep_buffer.nsamples > 0)
+ bleep_buffer.nsamples -= samples;
+
+ if(music_buffer.nsamples > samples) {
+ memmove(music_buffer.samples, music_buffer.samples+samples,
+ sizeof(float)*(music_buffer.nsamples-samples));
+ }
+ if(music_buffer.nsamples > 0)
+ music_buffer.nsamples -= samples;
+
+
+ samplecount = samples;
+ floattopcm16(shortbuffer, outbuf, samples);
+ ao_play(device, (char *) shortbuffer, samplecount * sizeof(short));
+ free(outbuf);
+ }
+
+ if (!bleep_playing && !music_playing) {
+ ao_close(device);
+ device = NULL;
+ }
+
+ pthread_mutex_unlock(&mutex); /* release the mutex lock */
+
+ if(bleep_buffer.nsamples) {
+ sem_post(&bleep_buffer.full);
+ }
+ if(music_buffer.nsamples) {
+ sem_post(&music_buffer.full);
+ }
+
+ int tmp;
+ sem_getvalue(&bleep_buffer.empty, &tmp);
+
+ if(bleep_buffer.nsamples <= 0 && tmp == 0) {
+ sem_post(&bleep_buffer.empty); /* signal empty */
+ }
+
+ sem_getvalue(&music_buffer.empty, &tmp);
+ if(music_buffer.nsamples <= 0 && tmp == 0) {
+ sem_post(&music_buffer.empty); /* signal empty */
+ }
+ }
+ } /* mixer */
+
+
+ /* Convert back to shorts */
+ static void floattopcm16(short *outbuf, float *inbuf, int length)
+ {
+ int count;
+
+ const float mul = (32768.0f);
+ for (count = 0; count <= length; count++) {
+ int32_t tmp = (int32_t)(mul * inbuf[count]);
+ tmp = MAX( tmp, -32768 ); // CLIP < 32768
+ tmp = MIN( tmp, 32767 ); // CLIP > 32767
+ outbuf[count] = tmp;
+ }
+ }
+
+
+ /* Convert the buffer to floats. (before resampling) */
+ static void pcm16tofloat(float *outbuf, short *inbuf, int length)
+ {
+ int count;
+
+ const float div = (1.0f/32768.0f);
+ for (count = 0; count <= length; count++) {
+ outbuf[count] = div * (float) inbuf[count];
+ }
+ }
+
+
+ /*
+ * stereoize
+ *
+ * Copy the single channel of a monaural stream to both channels
+ * of a stereo stream.
+ *
+ */
+ static void stereoize(float *outbuf, float *inbuf, size_t length)
+ {
+ size_t count;
+ int outcount;
+
+ outcount = 0;
+
+ for (count = 0; count < length; count++) {
+ outbuf[outcount] = outbuf[outcount+1] = inbuf[count];
+ outcount += 2;
+ }
+ }
+
+
+ /*
+ * mypower
+ *
+ * Just a simple recursive integer-based power function because I don't
+ * want to use the floating-point version from libm.
+ *
+ */
+ static int mypower(int base, int exp) {
+ if (exp == 0)
+ return 1;
+ else if (exp % 2)
+ return base * mypower(base, exp - 1);
+ else {
+ int temp = mypower(base, exp / 2);
+ return temp * temp;
+ }
+ }
+
+
+ /*
+ * playaiff
+ *
+ * This function takes a file pointer to a Blorb file and a bb_result_t
+ * struct describing what chunk to play. It's up to the caller to make
+ * sure that an AIFF chunk is to be played. Volume and repeats are also
+ * handled here.
+ *
+ * This function should be able to play OGG chunks, but because of a bug
+ * or oversight in Libsndfile, that library is incapable of playing OGG
+ * data which are embedded in a larger file.
+ *
+ */
+ void *playaiff(EFFECT *raw_effect)
+ {
+ // long filestart;
+
+ int volcount;
+ int volfactor;
+
+ float *floatbuffer;
+ float *floatbuffer2;
+
+ SNDFILE *sndfile;
+ SF_INFO sf_info;
+
+ SRC_STATE *src_state;
+ SRC_DATA src_data;
+ int error;
+ sf_count_t output_count = 0;
+
+ EFFECT myeffect = *raw_effect;
+
+ sem_post(&playaiff_okay);
+
+ sf_info.format = 0;
+ bleepnum = myeffect.number;
+
+ // filestart = ftell(myeffect.fp);
+ lseek(fileno(myeffect.fp), myeffect.result.data.startpos, SEEK_SET);
+ sndfile = sf_open_fd(fileno(myeffect.fp), SFM_READ, &sf_info, 0);
+
+ if (myeffect.vol < 1) myeffect.vol = 1;
+ if (myeffect.vol > 8) myeffect.vol = 8;
+ volfactor = mypower(2, -myeffect.vol + 8);
+
+ floatbuffer = malloc(BUFFSIZE * sf_info.channels * sizeof(float));
+ floatbuffer2 = malloc(BUFFSIZE * 2 * sizeof(float));
+ memset(bleep_buffer.samples, 0, BUFFSIZE * sizeof(float) * 2);
+
+ /* Set up for conversion */
+ if ((src_state = src_new(SRC_SINC_FASTEST, sf_info.channels, &error)) == NULL) {
+ printf("Error: src_new() failed: %s.\n", src_strerror(error));
+ exit(1);
+ }
+ src_data.end_of_input = 0;
+ src_data.input_frames = 0;
+ src_data.data_in = floatbuffer;
+ src_data.src_ratio = (1.0 * SAMPLERATE) / sf_info.samplerate;
+ src_data.data_out = floatbuffer2;
+ src_data.output_frames = BUFFSIZE / sf_info.channels;
+
+ bleep_playing = TRUE;
+
+ while (1) {
+ /* Check if we're being told to stop. */
+ if (!bleep_playing) break;
+ sem_wait(&bleep_buffer.empty);
+ pthread_mutex_lock(&mutex);
+
+ /* If floatbuffer is empty, refill it. */
+ if (src_data.input_frames == 0) {
+ src_data.input_frames = sf_readf_float(sndfile, floatbuffer, BUFFSIZE / sf_info.channels);
+ src_data.data_in = floatbuffer;
+ /* Mark end of input. */
+ if (src_data.input_frames < BUFFSIZE / sf_info.channels)
+ src_data.end_of_input = SF_TRUE;
+ }
+
+ /* Do the sample rate conversion. */
+ if ((error = src_process(src_state, &src_data))) {
+ printf("Error: %s\n", src_strerror(error));
+ exit(1);
+ }
+
+ bleep_buffer.nsamples = src_data.output_frames_gen * 2;
+
+ /* Stereoize monaural sound-effects. */
+ if (sf_info.channels == 1) {
+ /* Remember that each monaural frame contains just one sample. */
+ stereoize(bleep_buffer.samples, floatbuffer2, src_data.output_frames_gen);
+ } else {
+ /* It's already stereo. Just copy the buffer. */
+ memcpy(bleep_buffer.samples, floatbuffer2, sizeof(float) * src_data.output_frames_gen * 2);
+ }
+
+ /* Adjust volume. */
+ for (volcount = 0; volcount <= bleep_buffer.nsamples; volcount++)
+ bleep_buffer.samples[volcount] /= volfactor;
+
+ /* If that's all, terminate and signal that we're done. */
+ if (src_data.end_of_input && src_data.output_frames_gen == 0) {
+ sem_post(&bleep_buffer.full);
+ pthread_mutex_unlock(&mutex);
+ break;
+ }
+
+ /* Get ready for the next chunk. */
+ output_count += src_data.output_frames_gen;
+ src_data.data_in += src_data.input_frames_used * sf_info.channels;
+ src_data.input_frames -= src_data.input_frames_used;
+
+ /* By this time, the buffer is full. Signal the mixer to play it. */
+ pthread_mutex_unlock(&mutex);
+ sem_post(&bleep_buffer.full);
+ }
+
+ /* The two ways to exit the above loop are to process all the
+ * samples in the AIFF file or else get told to stop early.
+ * Whichever, we need to clean up and terminate this thread.
+ */
+
+ bleep_playing = FALSE;
+ memset(bleep_buffer.samples, 0, BUFFSIZE * sizeof(float) * 2);
+
+ // fseek(myeffect.fp, filestart, SEEK_SET);
+
+ // pthread_mutex_unlock(&mutex);
+ // sem_post(&audio_empty);
+
+ sf_close(sndfile);
+ free(floatbuffer);
+ free(floatbuffer2);
+
+ pthread_exit(NULL);
+ } /* playaiff */
+
+
+ /*
+ * playmusic
+ *
+ * To more easily make sure only one of MOD or OGGV plays at one time.
+ *
+ */
+ static void *playmusic(EFFECT *raw_effect)
+ {
+ EFFECT myeffect = *raw_effect;
+
+ sem_post(&playmusic_okay);
+
+ if (myeffect.type == MOD) playmod(&myeffect);
+ else if (myeffect.type == OGGV) playogg(&myeffect);
+ else { } /* do nothing */
+
+ pthread_exit(NULL);
+
+ } /* playmusic */
+
+
+ /*
+ * playmod
+ *
+ * This function takes a file pointer to a Blorb file and a bb_result_t
+ * struct describing what chunk to play. It's up to the caller to make
+ * sure that a MOD chunk is to be played. Volume and repeats are also
+ * handled here.
+ *
+ */
+ static void *playmod(EFFECT *raw_effect)
+ {
+ short *shortbuffer;
+
+ // int modlen;
+ // int count;
+
+ char *filedata;
+ long size;
+ ModPlugFile *mod;
+ ModPlug_Settings settings;
+
+ long filestart;
+
+ EFFECT myeffect = *raw_effect;
+
+ set_musicnum(myeffect.number);
+
+ filestart = ftell(myeffect.fp);
+ fseek(myeffect.fp, myeffect.result.data.startpos, SEEK_SET);
+
+ ModPlug_GetSettings(&settings);
+
+ /* Note: All "Basic Settings" must be set before ModPlug_Load. */
+ settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */
+ settings.mChannels = 2;
+ settings.mBits = 16;
+ settings.mFrequency = SAMPLERATE;
+ settings.mStereoSeparation = 128;
+ settings.mMaxMixChannels = 256;
+
+ /* insert more setting changes here */
+ ModPlug_SetSettings(&settings);
+
+ /* remember to free() filedata later */
+ filedata = getfiledata(myeffect.fp, &size);
+
+ mod = ModPlug_Load(filedata, size);
+ fseek(myeffect.fp, filestart, SEEK_SET);
+ if (!mod) {
+ printf("Unable to load MOD chunk.\n\r");
+ return 0;
+ }
+
+ if (myeffect.vol < 1) myeffect.vol = 1;
+ if (myeffect.vol > 8) myeffect.vol = 8;
+ ModPlug_SetMasterVolume(mod, mypower(2, myeffect.vol));
+
+ shortbuffer = malloc(BUFFSIZE * sizeof(short) * 2);
+
+ music_playing = TRUE;
+
+ while (1) {
+ sem_wait(&music_buffer.empty);
+ pthread_mutex_lock(&mutex);
+ memset(music_buffer.samples, 0, BUFFSIZE * sizeof(float) * 2);
+ if (!music_playing) {
+ break;
+ }
+ music_buffer.nsamples = ModPlug_Read(mod, shortbuffer, BUFFSIZE) / 2;
+ pcm16tofloat(music_buffer.samples, shortbuffer, music_buffer.nsamples);
+ if (music_buffer.nsamples == 0) break;
+ pthread_mutex_unlock(&mutex);
+ sem_post(&music_buffer.full);
+ }
+
+ music_playing = FALSE;
+ memset(music_buffer.samples, 0, BUFFSIZE * sizeof(float) * 2);
+
+ pthread_mutex_unlock(&mutex);
+ sem_post(&music_buffer.empty);
+
+ ModPlug_Unload(mod);
+ free(shortbuffer);
+ free(filedata);
+
+ return 0;
+ } /* playmod */
+
+
+ /*
+ * getfiledata
+ *
+ * libmodplug requires the whole file to be pulled into memory.
+ * This function does that and then closes the file.
+ */
+ static char *getfiledata(FILE *fp, long *size)
+ {
+ char *data;
+ long offset;
+
+ offset = ftell(fp);
+ fseek(fp, 0L, SEEK_END);
+ (*size) = ftell(fp);
+ fseek(fp, offset, SEEK_SET);
+ data = (char*)malloc(*size);
+ fread(data, *size, sizeof(char), fp);
+ fseek(fp, offset, SEEK_SET);
+ return(data);
+ } /* getfiledata */
+
+
+ /*
+ * playogg
+ *
+ * This function takes a file pointer to a Blorb file and a bb_result_t
+ * struct describing what chunk to play. It's up to the caller to make
+ * sure that an OGG chunk is to be played. Volume and repeats are also
+ * handled here.
+ *
+ * Libsndfile is capable of reading OGG files, but not if the file is
+ * embedded in another file. That's why we're using libvorbisfile
+ * directly instead of going through libsndfile. Erikd, main developer
+ * of libsndfile is working on a fix.
+ *
+ */
+ static void *playogg(EFFECT *raw_effect)
+ {
+ ogg_int64_t toread;
+ ogg_int64_t frames_read;
+ ogg_int64_t count;
+
+ vorbis_info *info;
+
+ OggVorbis_File vf;
+
+ int current_section;
+ short *shortbuffer;
+
+ // long filestart;
+ int volcount;
+ int volfactor;
+
+ EFFECT myeffect = *raw_effect;
+
+ // filestart = ftell(myeffect.fp);
+ fseek(myeffect.fp, myeffect.result.data.startpos, SEEK_SET);
+
+ if (ov_open_callbacks(myeffect.fp, &vf, NULL, 0, OV_CALLBACKS_NOCLOSE) < 0) {
+ printf("Unable to load OGGV chunk.\n\r");
+ return 0;
+ }
+
+ info = ov_info(&vf, -1);
+ if (info == NULL) {
+ printf("Unable to get info on OGGV chunk.\n\r");
+ return 0;
+ }
+
+ if (myeffect.vol < 1) myeffect.vol = 1;
+ if (myeffect.vol > 8) myeffect.vol = 8;
+ volfactor = mypower(2, -myeffect.vol + 8);
+
+ shortbuffer = malloc(BUFFSIZE * info->channels * sizeof(short));
+
+ frames_read = 0;
+ toread = ov_pcm_total(&vf, -1) * 2 * info->channels;
+ count = 0;
+
+ music_playing = TRUE;
+
+ while (count < toread) {
+ sem_wait(&music_buffer.empty);
+ pthread_mutex_lock(&mutex);
+ memset(music_buffer.samples, 0, BUFFSIZE * sizeof(float) * 2);
+ if (!music_playing) break;
+
+ frames_read = ov_read(&vf, (char *)shortbuffer, BUFFSIZE, 0,2,1,¤t_section);
+
+ pcm16tofloat(music_buffer.samples, shortbuffer, frames_read);
+ for (volcount = 0; volcount <= frames_read / 2; volcount++) {
+ ((float *) music_buffer.samples)[volcount] /= volfactor;
+ }
+
+ music_buffer.nsamples = frames_read / 2;
+ if(music_buffer.nsamples == -1)
+ music_buffer.nsamples = 0;
+ //perform mix down
+ count += frames_read;
+
+ pthread_mutex_unlock(&mutex);
+ sem_post(&music_buffer.full);
+ }
+
+ // fseek(myeffect.fp, filestart, SEEK_SET);
+ music_playing = FALSE;
+
+ pthread_mutex_unlock(&mutex);
+ sem_post(&music_buffer.empty);
+
+ ov_clear(&vf);
+
+ free(shortbuffer);
+
+ return 0;
+ } /* playogg */
+
+ #endif /* NO_SOUND */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
- #include <getopt.h>
+
+ #include <unistd.h>
+ #include <libgen.h>
#include <math.h>
- #include <errno.h>
- #include "../common/frotz.h"
- #include "../blorb/blorb.h"
- #include "../blorb/blorblow.h"
+ #ifdef USE_NCURSES_H
+ #include <ncurses.h>
+ #else
+ #include <curses.h>
+ #endif
+
+ #include "ux_frotz.h"
#include "ux_blorb.h"
-// uint32 *findchunk(uint32 *data, char *chunkID, int length);
-// static char *findchunk(char *pstart, char *fourcc, int n);
-// static unsigned short ReadShort(const unsigned char *bytes);
-// static unsigned long ReadLong(const unsigned char *bytes);
-// static double ReadExtended(const unsigned char *bytes);
+ f_setup_t f_setup;
+ u_setup_t u_setup;
+
+ FILE *blorb_fp;
+ bb_result_t blorb_res;
+ bb_map_t *blorb_map;
+
+ static int isblorb(FILE *);
+
+ #define UnsignedToFloat(u) (((double)((long)(u - 2147483647L - 1))) + 2147483648.0)
- char *findchunk(char *data, char *string, int length)
+
+ /*
+ * ux_blorb_init
+ *
+ * Check if we're opening a Blorb file directly. If not, check
+ * to see if there's a seperate Blorb file that looks like it goes
+ * along with this Zcode file. If we have a Blorb file one way or the
+ * other, make a Blorb map. If we opened a Blorb file directly, that
+ * means that our executable is in that file and therefore we will look
+ * for a ZCOD chunk and record its location so os_load_story() can find it.
+ * Make sure the Blorb file is opened and with the file pointer blorb_fp.
+ */
+ bb_err_t ux_blorb_init(char *filename)
{
- char *mydata = data+12;
- while (TRUE) {
- if (strncmp((char*)mydata, string, 4) == 0)
- return mydata+8;
+ FILE *fp;
+ char *p;
+ char *mystring;
+ int len1;
+ int len2;
+
+ bb_err_t blorb_err;
+
+ blorb_map = NULL;
+
+ if ((fp = fopen(filename, "rb")) == NULL)
+ return bb_err_Read;
+
+ /* Is this really a Blorb file? If not, maybe we're loading a naked
+ * zcode file and our resources are in a seperate blorb file.
+ */
+ if (isblorb(fp)) { /* Now we know to look */
+ u_setup.exec_in_blorb = 1; /* for zcode in the blorb */
+ blorb_fp = fopen(filename, "rb");
+ } else {
+ len1 = strlen(filename) + strlen(EXT_BLORB);
+ len2 = strlen(filename) + strlen(EXT_BLORB3);
+
+ mystring = malloc(len2 * sizeof(char) + 1);
+ strncat(mystring, filename, len1 * sizeof(char));
+ p = rindex(mystring, '.');
+ *p = '\0';
- mydata += ReadLong(mydata+4)+8;
+ strncat(mystring, EXT_BLORB, len1 * sizeof(char));
- if ((mydata - data) >= length)
- break;
+ /* Done monkeying with the initial file. */
+ fclose(fp);
+ fp = NULL;
+
+ /* Check if foo.blb is there. */
+ if ((blorb_fp = fopen(mystring, "rb")) == NULL) {
+ p = rindex(mystring, '.');
+ *p = '\0';
+ strncat(mystring, EXT_BLORB3, len2 * sizeof(char));
+ blorb_fp = fopen(mystring, "rb");
}
- return NULL;
- }
+ if (blorb_fp == NULL || !isblorb(fp)) /* No matching blorbs found. */
+ return bb_err_NoBlorb;
- unsigned short ReadShort(const unsigned char *bytes)
- {
- return (unsigned short)(
- ((unsigned short)(bytes[0] & 0xFF) << 8) |
- ((unsigned short)(bytes[1] & 0xFF)));
+ /* At this point we know that we're using a naked zcode file */
+ /* with resources in a seperate Blorb file. */
+ u_setup.use_blorb = 1;
+ }
+
+ /* Create a Blorb map from this file.
+ * This will fail if the file is not a valid Blorb file.
+ * From this map, we can now pick out any resource we need.
+ */
+ blorb_err = bb_create_map(fp, &blorb_map);
+ if (blorb_err != bb_err_None)
+ return bb_err_Format;
+
+ /* Locate the EXEC chunk within the blorb file and record its
+ * location so os_load_story() can find it.
+ */
+ if (u_setup.exec_in_blorb) {
+ blorb_err = bb_load_chunk_by_type(blorb_map, bb_method_FilePos,
+ &blorb_res, bb_make_id('Z','C','O','D'), 0);
+ u_setup.exec_in_blorb = 1;
+ }
+
+ fclose(fp);
+ return blorb_err;
}
- unsigned long ReadLong(const unsigned char *bytes)
+
+ /*
+ * ux_blorb_stop
+ *
+ * Basically just close the Blorb file.
+ *
+ */
+ void ux_blorb_stop(void)
{
- return (unsigned long)(
- ((unsigned long)(bytes[0] & 0xFF) << 24) |
- ((unsigned long)(bytes[1] & 0xFF) << 16) |
- ((unsigned long)(bytes[2] & 0xFF) << 8) |
- ((unsigned long)(bytes[3] & 0xFF)));
+ if (blorb_fp != NULL)
+ fclose(blorb_fp);
+ blorb_fp = NULL;
}
- double ReadExtended(const unsigned char *bytes)
+ /*
+ **********************************************
+ * These functions are internal to ux_blorb.c
+ *
+ **********************************************
+ */
+
+ /*
+ * isblorb
+ *
+ * Returns 1 if this file is a Blorb file, 0 if not.
+ *
+ * FIXME Is there a potential endian problem here?
+ */
+ static int isblorb(FILE *fp)
{
- double f;
- int expon;
- unsigned long hiMant, loMant;
-
- expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF);
- hiMant = ReadLong(bytes+2);
- loMant = ReadLong(bytes+6);
-
- if (expon == 0 && hiMant == 0 && loMant == 0)
- f = 0;
- else {
- if (expon == 0x7FFF) /* Infinity or NaN */
- f = -1;
- else {
- expon -= 16383;
- /* must #include <math.h> or these won't work */
- f = ldexp(UnsignedToFloat(hiMant),expon -= 31);
- f += ldexp(UnsignedToFloat(loMant),expon -= 32);
- }
- }
+ char mybuf[4];
+
+ if (fp == NULL)
+ return 0;
+
+ fread(mybuf, 1, 4, fp);
+ if (strncmp(mybuf, "FORM", 4))
+ return 0;
+
+ fseek(fp, 4, SEEK_CUR);
+ fread(mybuf, 1, 4, fp);
+
+ if (strncmp(mybuf, "IFRS", 4))
+ return 0;
- if (bytes[0] & 0x80)
- return -f;
- return f;
+ return 1;
}
-
-#ifdef CRAP
-static char *findchunk(char *data, char *string, int length)
-{
- char *mydata = data+12;
- while (TRUE) {
- if (strncmp((char*)mydata, string, 4) == 0)
- return mydata+8;
- mydata += ReadLong(mydata+4)+8;
- if ((mydata - data) >= length)
- break;
- }
- return NULL;
-}
-
-static unsigned short ReadShort(const unsigned char *bytes)
-{
- return (unsigned short)(
- ((unsigned short)(bytes[0] & 0xFF) << 8) |
- ((unsigned short)(bytes[1] & 0xFF)));
-}
-
-static unsigned long ReadLong(const unsigned char *bytes)
-{
- return (unsigned long)(
- ((unsigned long)(bytes[0] & 0xFF) << 24) |
- ((unsigned long)(bytes[1] & 0xFF) << 16) |
- ((unsigned long)(bytes[2] & 0xFF) << 8) |
- ((unsigned long)(bytes[3] & 0xFF)));
-}
-
-static double ReadExtended(const unsigned char *bytes)
-{
- double f;
- int expon;
- unsigned long hiMant, loMant;
-
- expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF);
- hiMant = ReadLong(bytes+2);
- loMant = ReadLong(bytes+6);
-
- if (expon == 0 && hiMant == 0 && loMant == 0)
- f = 0;
- else {
- if (expon == 0x7FFF) /* Infinity or NaN */
- f = -1;
- else {
- expon -= 16383;
- /* must #include <math.h> or these won't work */
- f = ldexp(UnsignedToFloat(hiMant),expon -= 31);
- f += ldexp(UnsignedToFloat(loMant),expon -= 32);
- }
- }
-
- if (bytes[0] & 0x80)
- return -f;
- return f;
-}
-#endif
*
*/
+ #include "defines.h"
#include "../common/frotz.h"
#include "../blorb/blorb.h"
+ #include "../blorb/blorblow.h"
#include "ux_setup.h"
-
+ #ifndef rindex
+ #define rindex strrchr
+ #endif
+
+ #define MASTER_CONFIG "frotz.conf"
+ #define USER_CONFIG ".frotzrc"
#define ASCII_DEF 1
#define ATTRIB_ASSIG_DEF 0
#define ATTRIB_TEST_DEF 0
void unix_save_screen(int); /* ux_screen.c */
void unix_do_scrollback(void); /* ux_screen.c */
- FILE *pathopen(const char *, const char *, const char *, char *);
-
-void sigwinch_handler(int);
-void sigint_handler(int);
--
+#ifdef NO_STRRCHR
+char *strrchr(const char *, int);
+#endif
#ifdef NO_MEMMOVE
void *memmove(void *, void *);
f_setup_t f_setup;
u_setup_t u_setup;
- static void sigwinch_handler(int);
+static int getconfig(char *);
+static int geterrmode(char *);
+static int getcolor(char *);
+static int getbool(char *);
+
- static void redraw(void);
++/* static void sigwinch_handler(int); */
+static void sigint_handler(int);
++/* static void redraw(void); */
+
+
#define INFORMATION "\
An interpreter for all Infocom and other Z-Machine games.\n\
Complies with standard 1.0 of Graham Nelson's specification.\n\
void os_process_arguments (int argc, char *argv[])
{
int c;
-
char *p = NULL;
-- char *blorb_ext = NULL;
++// FIXME: put this back before committing merge fixes
++// char *blorb_ext = NULL;
char *home;
char configfile[FILENAME_MAX + 1];
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
signal(SIGINT, sigint_handler);
- if (getenv("XDG_CONFIG_HOME")) {
- snprintf(configfile, FILENAME_MAX,
- "%s/frotz/frotz.conf", getenv("XDG_CONFIG_HOME"));
- } else {
- snprintf(configfile, FILENAME_MAX,
- "%s/.config/frotz/frotz.conf", home);
- }
+
- if (!getconfig(configfile)) {
- snprintf(configfile, FILENAME_MAX, "%s/.frotzrc", home);
- }
-
- if (!getconfig(configfile)) {
- snprintf(configfile, FILENAME_MAX, "%s/frotz.conf", CONFIG_DIR);
- getconfig(configfile); /* we're not concerned if this fails */
- }
+ if (signal(SIGTTIN, SIG_IGN) != SIG_IGN)
+ signal(SIGTTIN, SIG_IGN);
- /* Parse the options */
+ if (signal(SIGTTOU, SIG_IGN) != SIG_IGN)
+ signal(SIGTTOU, SIG_IGN);
+
+ /* First check for a "$HOME/.frotzrc". */
+ /* If not found, look for CONFIG_DIR/frotz.conf */
+ /* $HOME/.frotzrc overrides CONFIG_DIR/frotz.conf */
+
+ strncpy(configfile, home, FILENAME_MAX);
+ strncat(configfile, "/", 1);
- strncpy(configfile, CONFIG_DIR, FILENAME_MAX);
- strncat(configfile, "/", 1); /* added by DJP */
- strncat(configfile, MASTER_CONFIG, FILENAME_MAX-10);
- getconfig(configfile); /* we're not concerned if this fails */
+ strncat(configfile, USER_CONFIG, strlen(USER_CONFIG));
+ if (!getconfig(configfile)) {
++ strncpy(configfile, CONFIG_DIR, FILENAME_MAX);
++ strncat(configfile, "/", 1); /* added by DJP */
++ strncat(configfile, MASTER_CONFIG, FILENAME_MAX-10);
++ getconfig(configfile); /* we're not concerned if this fails */
+ }
+
+ /* Parse the options */
do {
- c = getopt(argc, argv, "aAb:c:def:Fh:il:oOpPqrR:s:S:tu:w:xZ:");
- c = zgetopt(argc, argv, "aAb:c:def:Fh:il:oOpPqr:s:S:tu:vw:xZ:");
++ c = zgetopt(argc, argv, "aAb:c:def:Fh:il:oOpPqrR:s:S:tu:w:xZ:");
switch(c) {
case 'a': f_setup.attribute_assignment = 1; break;
case 'A': f_setup.attribute_testing = 1; break;
--
- case 'b': u_setup.background_color = getcolor(optarg);
+ case 'b': u_setup.background_color = getcolor(zoptarg);
u_setup.force_color = 1;
u_setup.disable_color = 0;
if ((u_setup.background_color < 2) ||
case 'p': u_setup.plain_ascii = 1; break;
case 'P': f_setup.piracy = 1; break;
case 'q': f_setup.sound = 0; break;
- case 'r': f_setup.right_margin = atoi(optarg); break;
+ case 'r': f_setup.right_margin = atoi(zoptarg); break;
+ case 'R': f_setup.restore_mode = 1;
+ f_setup.tmp_save_name = malloc(FILENAME_MAX * sizeof(char) + 1);
- strncpy(f_setup.tmp_save_name, optarg, FILENAME_MAX);
++ strncpy(f_setup.tmp_save_name, zoptarg, FILENAME_MAX);
+ break;
- case 's': u_setup.random_seed = atoi(optarg); break;
- case 'S': f_setup.script_cols = atoi(optarg); break;
+ case 's': u_setup.random_seed = atoi(zoptarg); break;
+ case 'S': f_setup.script_cols = atoi(zoptarg); break;
case 't': u_setup.tandy_bit = 1; break;
- case 'u': f_setup.undo_slots = atoi(optarg); break;
- case 'w': u_setup.screen_width = atoi(optarg); break;
+ case 'u': f_setup.undo_slots = atoi(zoptarg); break;
+ case 'v': print_version(); exit(2); break;
+ case 'w': u_setup.screen_width = atoi(zoptarg); break;
case 'x': f_setup.expand_abbreviations = 1; break;
- case 'Z': f_setup.err_report_mode = atoi(optarg);
+ case 'Z': f_setup.err_report_mode = atoi(zoptarg);
if ((f_setup.err_report_mode < ERR_REPORT_NEVER) ||
(f_setup.err_report_mode > ERR_REPORT_FATAL))
f_setup.err_report_mode = ERR_DEFAULT_REPORT_MODE;
if ((p != NULL) &&
((strcmp(p,EXT_BLORB2) == 0) ||
(strcmp(p,EXT_BLORB3) == 0) ||
- (strcmp(p,EXT_BLORB4) == 0) ) )
- {
- blorb_ext = strdup(p);
+ (strcmp(p,EXT_BLORB4) == 0) ) ) {
- blorb_ext = strdup(p);
++// blorb_ext = strdup(p);
}
else
- {
- blorb_ext = strdup(EXT_BLORB);
- }
- blorb_ext = strdup(EXT_BLORB);
++// blorb_ext = strdup(EXT_BLORB);
+
/* Get rid of extensions with 1 to 6 character extensions. */
/* This will take care of an extension like ".zblorb". */
/* More than that, there might be something weird going on */
/* which is not our concern. */
if (p != NULL) {
- if (strlen(p) >= 2 && strlen(p) <= 7) {
- *p = '\0'; /* extension removed */
- }
+ if (strlen(p) >= 2 && strlen(p) <= 7) {
+ *p = '\0'; /* extension removed */
+ }
}
- f_setup.story_path = strdup(dirname(argv[optind]));
-
- /* Create nice default file names */
-
- u_setup.blorb_name = malloc(FILENAME_MAX * sizeof(char));
- strncpy(u_setup.blorb_name, f_setup.story_name,
- strlen(f_setup.story_name) +1);
- strncat(u_setup.blorb_name, blorb_ext, strlen(blorb_ext));
+ f_setup.story_path = strdup(dirname(argv[zoptind]));
- u_setup.blorb_file = malloc(strlen(f_setup.story_path) *
- sizeof(char) + strlen(u_setup.blorb_name) * sizeof(char) + 4);
- strncpy(u_setup.blorb_file, f_setup.story_path,
- strlen(f_setup.story_path));
- strncat(u_setup.blorb_file, "/", 1);
- strncat(u_setup.blorb_file, u_setup.blorb_name,
- strlen(u_setup.blorb_name) + 1);
-
- f_setup.script_name = malloc(strlen(f_setup.story_name) * sizeof(char) + 5);
- strncpy(f_setup.script_name, f_setup.story_name, strlen(f_setup.story_name));
+ f_setup.script_name = malloc((strlen(f_setup.story_name) + strlen(EXT_SCRIPT)) * sizeof(char) + 1);
+ strncpy(f_setup.script_name, f_setup.story_name, strlen(f_setup.story_name) + 1);
strncat(f_setup.script_name, EXT_SCRIPT, strlen(EXT_SCRIPT));
- f_setup.command_name = malloc(strlen(f_setup.story_name) * sizeof(char) + 5);
- strncpy(f_setup.command_name, f_setup.story_name, strlen(f_setup.story_name));
+ f_setup.command_name = malloc((strlen(f_setup.story_name) + strlen(EXT_COMMAND)) * sizeof(char) + 1);
+ strncpy(f_setup.command_name, f_setup.story_name, strlen(f_setup.story_name) + 1);
strncat(f_setup.command_name, EXT_COMMAND, strlen(EXT_COMMAND));
- f_setup.save_name = malloc((strlen(f_setup.story_name) + strlen(EXT_SAVE)) * sizeof(char) + 1);
- strncpy(f_setup.save_name, f_setup.story_name, strlen(f_setup.story_name) + 1);
- strncat(f_setup.save_name, EXT_SAVE, strlen(EXT_SAVE));
+ if (!f_setup.restore_mode) {
+ f_setup.save_name = malloc(strlen(f_setup.story_name) * sizeof(char) + 5);
+ strncpy(f_setup.save_name, f_setup.story_name, strlen(f_setup.story_name));
+ strncat(f_setup.save_name, EXT_SAVE, strlen(EXT_SAVE));
+ } else { /*Set our auto load save as the name_save*/
+ f_setup.save_name = malloc(strlen(f_setup.tmp_save_name) * sizeof(char) + 5);
+ strncpy(f_setup.save_name, f_setup.tmp_save_name, strlen(f_setup.tmp_save_name));
+ free(f_setup.tmp_save_name);
+ }
- f_setup.aux_name = malloc(strlen(f_setup.story_name) * sizeof(char) + 5);
- strncpy(f_setup.aux_name, f_setup.story_name, strlen(f_setup.story_name));
+ f_setup.aux_name = malloc((strlen(f_setup.story_name) + strlen(EXT_AUX)) * sizeof(char) + 1);
+ strncpy(f_setup.aux_name, f_setup.story_name, strlen(f_setup.story_name) + 1);
strncat(f_setup.aux_name, EXT_AUX, strlen(EXT_AUX));
- if (strncmp(basename(f_setup.story_file),
- basename(u_setup.blorb_file), 55)) {
- return;
- } else if (!(blorbfile = fopen(u_setup.blorb_file, "rb"))) {
- fprintf(stderr, "Error: Cannot read blorb file %s.\n", u_setup.blorb_file);
- } else if (bb_create_map(blorbfile, &blorb_map) != bb_err_None) {
- fputs("Error: Blorb file loaded, but unable to build map.\n", stderr );
- } else if (bb_load_chunk_by_type(blorb_map, bb_method_FilePos,
- &blorb_res, bb_make_id('Z','C','O','D'), 0) != bb_err_None) {
- fputs("Error: Blorb file loaded, but lacks executable chunk.\n", stderr);
- } else {
- u_setup.exec_in_blorb = 1;
- u_setup.use_blorb = 1;
- }
}/* os_process_arguments */
+
/*
* os_init_screen
*
* cleanly resize the window.
*
*/
-
- static void sigwinch_handler(int sig)
-void sigwinch_handler(int UNUSED(sig))
--{
++// FIXME: figure out what to do with this
++//static void sigwinch_handler(int UNUSED(sig))
++//{
/*
There are some significant problems involved in getting resizes to work
properly with at least this implementation of the Z Machine and probably
explaination for this. Because of this trouble, this function currently
does nothing.
*/
-}
++//}
- }
/*
* sigint_handler
* is not done.
*
*/
-void sigint_handler(int dummy)
+static void sigint_handler(int dummy)
{
signal(SIGINT, sigint_handler);
+ dummy = dummy;
+ os_stop_sample(0);
scrollok(stdscr, TRUE); scroll(stdscr);
refresh(); endwin();
}
- }
+#ifdef NO_STRRCHR
+/*
+ * This is for operating systems that lack strrchr(3).
+ *
+ */
+char *strrchr(const char *s, int c)
+{
+ const char *save;
+
+ if (c == 0) return (char *)s + strlen(s);
+ save = 0;
+ while (*s) {
+ if (*s == c)
+ save = s;
+ s++;
+ }
+ return (char *)save;
++#endif /* NO_STRRCHR */
++
+
+ /* A unix-like getopt, but with the names changed to avoid any problems. */
+ static int zgetopt (int argc, char *argv[], const char *options)
+ {
+ static int pos = 1;
+ const char *p;
+ if (zoptind >= argc || argv[zoptind][0] != '-' || argv[zoptind][1] == 0)
+ return EOF;
+ zoptopt = argv[zoptind][pos++];
+ zoptarg = NULL;
+ if (argv[zoptind][pos] == 0) {
+ pos = 1;
+ zoptind++;
+ }
+ p = strchr (options, zoptopt);
+ if (zoptopt == ':' || p == NULL) {
+ fputs ("illegal option -- ", stderr);
+ goto error;
+ } else if (p[1] == ':') {
+ if (zoptind >= argc) {
+ fputs ("option requires an argument -- ", stderr);
+ goto error;
+ } else {
+ zoptarg = argv[zoptind];
+ if (pos != 1)
+ zoptarg += pos;
+ pos = 1; zoptind++;
+ }
+ }
+ return zoptopt;
+ error:
+ fputc (zoptopt, stderr);
+ fputc ('\n', stderr);
+ return '?';
+ }/* zgetopt */
+
+
+ static void print_version(void)
+ {
+ printf("FROTZ V%s\t", VERSION);
+ #ifndef NO_SOUND
+ printf("Audio output enabled.");
+ #else
+ printf("Audio output disabled.");
#endif
+ printf("\nGit commit:\t%s\n", GIT_HASH);
+ printf("Git tag:\t%s\n", GIT_TAG);
+ printf("Git branch:\t%s\n", GIT_BRANCH);
+ printf(" Frotz was originally written by Stefan Jokisch\n");
+ printf(" It was ported to Unix by Galen Hazelwood.\n");
+ printf(" The core and Unix port are currently maintained by David Griffith.\n");
+ printf(" See https://github.com/DavidGriffith/frotz for Frotz's homepage\n\n");
+ return;
+ }
#include "ux_frotz.h"
++static int start_of_prev_word(int, const zchar*);
++static int end_of_next_word(int, const zchar*, int);
++
static struct timeval global_timeout;
/* Some special characters. */
case MOD_META | 'x': return ZC_HKEY_QUIT;
case MOD_META | 'd': return ZC_HKEY_DEBUG;
case MOD_META | 'h': return ZC_HKEY_HELP;
+ case MOD_META | 'f': return ZC_WORD_RIGHT;
+ case MOD_META | 'b': return ZC_WORD_LEFT;
- /* these are the emacs-editing characters */
+ /* these are the emacs-editing characters */
case MOD_CTRL ^ 'B': return ZC_ARROW_LEFT;
case MOD_CTRL ^ 'F': return ZC_ARROW_RIGHT;
case MOD_CTRL ^ 'P': return ZC_ARROW_UP;
}
#endif /* NO_MEMMOVE */
- int start_of_prev_word(int currpos, const zchar* buf) {
+
+
+/*
+ * Search for start of preceding word
+ * param currpos marker position
+ * param buf input buffer
+ * returns new position
+ */
- int end_of_next_word(int currpos, const zchar* buf, int len) {
- int i, j;
++static int start_of_prev_word(int currpos, const zchar* buf) {
+ int i, j;
+ for (i = currpos - 1; i > 0 && buf[i] == ' '; i--) {}
+ j = i;
+ for (; i > 0 && buf[i] != ' '; i--) {}
+ if (i < j && i != 0) {
+ i += 1;
+ }
+ return i;
+}
+
+/*
+ * Search for end of next word
+ * param currpos marker position
+ * param buf input buffer
+ * param len length of buf
+ * returns new position
+ */
- j = i;
++static int end_of_next_word(int currpos, const zchar* buf, int len) {
++ int i;
+ for (i = currpos; i < len && buf[i] == ' '; i++) {}
+ for (; i < len && buf[i] != ' '; i++) {}
+ return i;
+}
f_setup.script_cols = 80;
f_setup.sound = 1;
f_setup.err_report_mode = ERR_DEFAULT_REPORT_MODE;
+ f_setup.restore_mode = 0;
+
+}
-
+char *my_strdup(char *src)
+{
+ char *str;
+ char *p;
+ int len = 0;
+
+ while (src[len])
+ len++;
+ str = malloc(len + 1);
+ p = str;
+ while (*src)
+ *p++ = *src++;
+ *p = '\0';
+ return str;
}