Merge remote-tracking branch 'origin/ao-curses'
authorDavid Griffith <dave@661.org>
Wed, 22 Feb 2017 02:53:40 +0000 (18:53 -0800)
committerDavid Griffith <dave@661.org>
Wed, 22 Feb 2017 02:53:40 +0000 (18:53 -0800)
Conflicts:
.gitignore
ChangeLog
Makefile
PORTING
README
doc/dfrotz.6
doc/frotz.6
src/common/fastmem.c
src/common/frotz.h
src/common/main.c
src/common/process.c
src/common/screen.c
src/curses/ux_audio_none.c
src/curses/ux_blorb.c
src/curses/ux_file.c
src/curses/ux_frotz.h
src/curses/ux_init.c
src/curses/ux_screen.c
src/curses/ux_text.c
src/dos/bcinit.c
src/dumb/dumb_init.c

22 files changed:
1  2 
.gitignore
ChangeLog
Makefile
README
TODO
doc/dfrotz.6
doc/frotz.6
src/common/fastmem.c
src/common/frotz.h
src/common/sound.c
src/curses/ux_audio.c
src/curses/ux_audio_none.c
src/curses/ux_audio_oss.c
src/curses/ux_blorb.c
src/curses/ux_frotz.h
src/curses/ux_init.c
src/curses/ux_input.c
src/curses/ux_pic.c
src/curses/ux_screen.c
src/curses/ux_text.c
src/dumb/dumb_init.c
src/dumb/dumb_input.c

diff --cc .gitignore
index 83e077ffb7be03fe549778ee6184e97d1f076db8,f9dba8afdeca29bfb636309bd0ef2badcacad86a..67dd8c1f3e2639b225c9437ff835b0c9d0f18d1e
@@@ -1,12 -1,6 +1,11 @@@
  frotz
  dfrotz
  *.o
 +*.O
 +*.OBJ
 +*.BAK
 +*.SWP
 +*.DSK
  *.a
- defines.h
- /tags
- /tmp
+ src/*/defines.h
+ src/common/git_hash.h
diff --cc ChangeLog
index 1aeb68da3fcfca4a77b854900fc53971008a28a2,c83ff253da79be4d0b1b4409aa4d34b6fe4054b6..2314774e163772429a6dbd484643e1bf90e55794
+++ b/ChangeLog
@@@ -1,38 -1,26 +1,46 @@@
 -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:
  ===============================================================
diff --cc Makefile
index 46fce590ab2bdc7089ff2bc5f6d0b2c8f279b1c2,fc3fa67d33c448da9485a251db06dd380ca1e938..1d7a865a1ae8447adb454adc00c9d33b344b8cb3
+++ b/Makefile
@@@ -76,28 -59,7 +65,13 @@@ CURSES = -lncurse
  #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.
@@@ -227,21 -172,51 +184,51 @@@ all:    $(NAME) d$(NAME
  .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.
  #
@@@ -249,11 -224,11 +236,11 @@@ common_lib:     $(COMMON_TARGET
  $(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..."
@@@ -265,55 -240,41 +252,35 @@@ dumb_lib:       $(DUMB_TARGET
  $(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 \
diff --cc README
index 6dc62e61b48c0fc05c8cd2f237dc71939a58516c,e21ed64cca9d5d38af9b604e1b323b66df6dee3e..f844c40e8c8c6911a5c77af5db812f2d838b8ba6
--- 1/README
--- 2/README
+++ b/README
@@@ -1,4 -1,4 +1,4 @@@
- 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.
diff --cc TODO
index ee35e2949ad72b64dcadf3838af429dd31d117a7,250b641d0f082ad134a614ed62814ce1d4323c93..af871c6a194b94275c06e1972ca7ca16095ef45b
--- 1/TODO
--- 2/TODO
+++ b/TODO
@@@ -1,3 -1,17 +1,8 @@@
 -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.
  
diff --cc doc/dfrotz.6
index 8bd269479c81f1cdc3de9b598e98cf506e15a5f1,7f1c3d5ac882a3ce3968ab54b4ae480f0e9d7894..2d17bfc550ef6152472ccfcdbae704a86c95fd6c
@@@ -1,5 -1,5 +1,5 @@@
  .\" -*- 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)
  
diff --cc doc/frotz.6
index 5407cb42b9d263c23fc2b5f6ea3e04507d5b3f5d,a4c12c1724bf1204723090ce1758545b0599ac2f..42c014d7746e10ccd7bd6073f35a284f01a999f3
@@@ -1,5 -1,5 +1,5 @@@
  .\" -*- 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
  
index 75b042d7600d37ca10d4bbc7698bbb37e8df674b,5d770c41538692cd6442e4e9995077c2d6df908f..1e7879068953ec8a789baa9e7267e5235d4f3f75
@@@ -522,9 -446,10 +519,9 @@@ static void free_undo (int count
   * Close the story file and deallocate memory.
   *
   */
 -
  void reset_memory (void)
  {
-     if (story_fp)
+     if (story_fp != NULL)
        fclose (story_fp);
      story_fp = NULL;
  
Simple merge
Simple merge
index 0000000000000000000000000000000000000000,1c8bd11a411c8764316853de1f580cd629f93c38..4648ac5e111581421e67c3c2584c33f1610da9d1
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,879 +1,878 @@@
 -    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,&current_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 */
Simple merge
Simple merge
index 723ad8fadf74ebbc5526335db9e1118690b3ef59,29adefc3e9ba742adf0606899c950d038ec4ff70..59fa5fd020384ccb67464c97d188933da2242cae
  #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
index 1637e86764699621c9fc7d7d91e91c4d6200b4c2,4c15ebdee5c6e4f5222bf8b700b4efd50c02367a..f40ddd4b35cb6ad34d303fe006a163b67248b249
@@@ -5,10 -5,19 +5,18 @@@
   *
   */
  
+ #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
@@@ -90,11 -94,10 +93,9 @@@ void unix_init_scrollback(void);    /* ux_
  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 *);
index af46d3eb474aebc1d95b954c1eb7e2cb970967c5,8d564d1a341cc848695b04c23dc95244a40ca215..b85b7ad9d2efd46d2c6ad4431169614d1403190d
  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\
@@@ -148,10 -167,8 +177,9 @@@ void os_fatal (const char *s, ...
  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
   *
@@@ -993,9 -949,8 +970,9 @@@ static int geterrmode(char *value
   * 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
@@@ -1003,8 -958,8 +980,8 @@@ the Z-Machine standard itself.  See th
  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();
  
@@@ -1073,22 -1028,57 +1051,76 @@@ void os_init_setup(void
  
  }
  
- }
 +#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;
+ }
index ee9101179f9fc6329e63b256203df18bf3693976,97e0d014f193fd226d6693c20fc50bf16244d6b1..704abc0117aad95682ec5facc12e1a8d40abf726
@@@ -36,6 -36,6 +36,9 @@@
  
  #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. */
@@@ -227,10 -230,8 +235,10 @@@ static int unix_read_char(int extkeys
        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;
@@@ -714,36 -691,3 +731,35 @@@ void *memmove(void *s, void *t, size_t 
  }
  
  #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;
 +}
Simple merge
Simple merge
Simple merge
index 5ac463ac61c6ee4b62ea0ebea4e1146c66f84ad3,a4617b565ee58f98053e20c1285872e568cc10d7..dec3ac855522c8eb31ea15d2e95911042c93d2da
@@@ -232,23 -207,5 +231,22 @@@ void os_init_setup(void
        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;
  }
Simple merge