From: Marijn van Vliet Date: Sun, 19 Jun 2011 10:20:51 +0000 (+0200) Subject: Working on babel/library support X-Git-Tag: v0.9~73^2 X-Git-Url: https://git.stderr.nl/gitweb?p=projects%2Fchimara%2Fchimara.git;a=commitdiff_plain;h=fb72f54c425e715d804b6b90cd3f622c81dcd050 Working on babel/library support --- diff --git a/Makefile.am b/Makefile.am index 352b3d3..d78eef1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,7 +4,7 @@ if TARGET_ILIAD SUBDIRS = libchimara interpreters player po else -SUBDIRS = libchimara interpreters player tests docs po +SUBDIRS = libchimara interpreters player tests docs po babel endif chimaradocdir = $(datadir)/doc/chimara diff --git a/babel/MANIFEST b/babel/MANIFEST new file mode 100644 index 0000000..09048bd --- /dev/null +++ b/babel/MANIFEST @@ -0,0 +1,51 @@ +adrift.c Treaty of Babel module for Adrift +advsys.c Treaty of Babel module for AdvSys +alan.c Treaty of Babel module for Alan +agt.c Treaty of Babel module for AGT +babel.h babel program header +md5.h L. Peter Deutsch's md5 header +modules.h babel module registry +treaty.h Treaty of Babel header +treaty_builder.h Macros to build treaty modules +babel.c Babel main program +babel_handler.h Babel handler header file +babel_handler.c The babel handler api +babel_ifiction_functions.c Babel program-specific ifiction operations +babel_multi_functions.c Babel program-specific multi operations +babel_story_functions.c Babel program-specific story operations +blorb.c babel handler blorb module +executable.c Treaty of Bable module for executables +glulx.c Treaty of Babel module for glulx +hugo.c Draft Treaty of Babel module for hugo +ifiction.h babel ifiction header +ifiction.c babel ifiction api +level9.c Treaty of Babel module for level9 +magscrolls.c Magnetic Scrolls treaty module +makefile Provisional makefile +md5.c L. Peter Deutsch's md5 implementation +misc.c babel memory allocator +register.c babel module registry +register_ifiction.c babel module registry for ifiction API +tads.h Prototypes for tads2.c and tads3.c +tads.c Common functions for TADS modules +tads2.c Treaty of Babel module for tads2 +tads3.c Treaty of Babel module for tads3 +zcode.c Treaty of Babel module for zcode +README documentation +MANIFEST this file +extras/babel-cache.pl Perl demo of babel interaction +extras/babel-infocom.pl Special bundler for the infocom corpus +extras/babel-list.c Babel API demo +extras/babel-marry.pl Perl simple blorb encapsulator +extras/babel-wed.pl Perl single file blorb encapsulator +extras/hotload.c Dynamic loader replacement for register.c +extras/hotload.h Header file for hotload.c +extras/ifiction-aggregate.c Utility to combine multiple ifiction files +extras/ifiction-xtract.c Ifiction API demo +extras/simple-marry.c Simplified C version of babel-marry +babel-get/babel-get.c The babel-get application +babel-get/get_dir.c Directory source +babel-get/get_ifiction.c ifiction source +babel-get/get_story.c story file source +babel-get/get_url.c URL source +babel-get/makefile Makefile for babel-get diff --git a/babel/Makefile.am b/babel/Makefile.am new file mode 100644 index 0000000..1e0d78d --- /dev/null +++ b/babel/Makefile.am @@ -0,0 +1,36 @@ +noinst_LTLIBRARIES = libbabel.la libifiction.la libbabel_functions.la + +libbabel_la_SOURCES = babel_handler.c \ + register.c \ + misc.c \ + md5.c \ + zcode.c \ + magscrolls.c \ + blorb.c \ + glulx.c \ + hugo.c \ + agt.c \ + level9.c \ + executable.c \ + advsys.c \ + tads.c \ + tads2.c \ + tads3.c \ + adrift.c \ + alan.c \ + babel.h \ + babel_handler.h \ + md5.h \ + modules.h \ + tads.h \ + treaty_builder.h \ + treaty.h + +libifiction_la_SOURCES = ifiction.c ifiction.h \ + register_ifiction.c + +libbabel_functions_la_SOURCES = babel_story_functions.c \ + babel_ifiction_functions.c \ + babel_multi_functions.c + +-include $(top_srcdir)/git.mk diff --git a/babel/README b/babel/README new file mode 100644 index 0000000..b754cab --- /dev/null +++ b/babel/README @@ -0,0 +1,74 @@ +Version 0.2b, Treaty of Babel Revision 7 +This is the source code for babel, the Treaty of Babel analysis tool. + +Most of this code is (c) 2006 by L. Ross Raszewski + +The following files are public domain: +zcode.c +glulx.c +executable.c +level9.c +magscrolls.c +agt.c +hugo.c +advsys.c +misc.c +alan.c +adrift.c +treaty.h +treaty_builder.h + +The following files are Copyright (C) 1999, 2000, 2002 Aladdin Enterprises: +md5.c +md5.h + +And are used in accordance with their licenses. + +All other files are (c) 2006 by L. Ross Raszewski and are released under +the Creative Commons Attribution2.5 License. + +To view a copy of this license, visit +http://creativecommons.org/licenses/by/2.5/ or send a letter to + +Creative Commons, +543 Howard Street, 5th Floor, +San Francisco, California, 94105, USA. + + +To build babel: + +1. compile all the source files in this directory +2. link them together +3. the end + +For folks who find makefiles more useful than generalizations, there is a +makefile provided for babel. The makefile is currently configured for +Borland's 32-bit C compiler. Comment out those lines and uncomment the block +which follows for gcc. + +To compile babel-get, first compile babel, then do the same thing in the +babel-get directory. + +To compile ifiction-aggregate, ifiction-xtract, babel-list, and simple-marry, +first compile babel, then compile the relevant C file in the extras/ directory +(These may rely on #include files from the babel directory, so, for example, +to compile ifiction-aggregate, "gcc -c -I.. ifiction-aggregate.c"), then link the +opbject file to the babel and ifiction libraries (babel.lib and ifiction.lib +under Windows, babel.a and ifiction.a most everywhere else. eg. +"gcc -o ifiction-aggregate ifiction-aggregate.o ../babel.a ../ifiction.a") + +Babel is intended to accept contributions in the form of treaty modules +as defined by the treaty of babel section 2.3.2. + +These modules should use the declarations made in treaty.h. +The file treaty_builder.h generates a generic framework which simplifies +the task of writing treaty modules. Its use is not required for treaty +compliance, but it should prove useful. + +Parts of babel are intended for use in other programs. When adapting +babel's source, the files babel.c, babel_story_functions.c and +babel_ifiction_functions.c will probably not prove useful. However, you +may wish to use babel_handler, which provides a framework for loading a +story file, selecting the proper treaty modules, and seamlessly handling +blorb-wrapped files. + diff --git a/babel/adrift.c b/babel/adrift.c new file mode 100644 index 0000000..3718d6a --- /dev/null +++ b/babel/adrift.c @@ -0,0 +1,89 @@ +/* adrift.c Treaty of Babel module for Adrift files + * + * PROVISIONAL - Hold for someone else + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file + * may render it noncompliant with the Treaty of Babel + */ + +#define FORMAT adrift +#define HOME_PAGE "http://www.adrift.org.uk" +#define FORMAT_EXT ".taf" +#define NO_METADATA +#define NO_COVER + +#include "treaty_builder.h" + +#include +#include +#include + +/* VB RNG constants */ +#define VB_RAND1 0x43FD43FD +#define VB_RAND2 0x00C39EC3 +#define VB_RAND3 0x00FFFFFF +#define VB_INIT 0x00A09E86 +static int32 vbr_state; + +/* + Unobfuscates one byte from a taf file. This should be called on each byte + in order, as the ADRIFT obfuscation function is stately. + + The de-obfuscation algorithm works by xoring the byte with the next + byte in the sequence produced by the Visual Basic pseudorandom number + generator, which is simulated here. +*/ +static unsigned char taf_translate (unsigned char c) +{ + int32 r; + + vbr_state = (vbr_state*VB_RAND1+VB_RAND2) & VB_RAND3; + r=UCHAR_MAX * (unsigned) vbr_state; + r/=((unsigned) VB_RAND3)+1; + return r^c; +} + +static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) +{ + int adv; + unsigned char buf[4]; + unsigned char *sf=(unsigned char *)story_file; + vbr_state=VB_INIT; + + if (extent <12) return INVALID_STORY_FILE_RV; + + buf[3]=0; + /* Burn the first 8 bytes of translation */ + for(adv=0;adv<8;adv++) taf_translate(0); + /* Bytes 8-11 contain the Adrift version number in the formay N.NN */ + buf[0]=taf_translate(sf[8]); + taf_translate(0); + buf[1]=taf_translate(sf[10]); + buf[2]=taf_translate(sf[11]); + adv=atoi((char *) buf); + ASSERT_OUTPUT_SIZE(12); + sprintf(output,"ADRIFT-%03d-",adv); + return INCOMPLETE_REPLY_RV; + +} + +/* The claim algorithm for ADRIFT is to unobfuscate the first + seven bytes, and check for the word "Version". + It seems fairly unlikely that the obfuscated form of that + word would occur in the wild +*/ +static int32 claim_story_file(void *story_file, int32 extent) +{ + unsigned char buf[8]; + int i; + unsigned char *sf=(unsigned char *)story_file; + buf[7]=0; + vbr_state=VB_INIT; + if (extent<12) return INVALID_STORY_FILE_RV; + for(i=0;i<7;i++) buf[i]=taf_translate(sf[i]); + if (strcmp((char *)buf,"Version")) return INVALID_STORY_FILE_RV; + return VALID_STORY_FILE_RV; + +} diff --git a/babel/advsys.c b/babel/advsys.c new file mode 100644 index 0000000..e0e1cf8 --- /dev/null +++ b/babel/advsys.c @@ -0,0 +1,49 @@ +/* advsys.c Treaty of Babel module for AdvSys files + * 2006 By L. Ross Raszewski + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file + * may render it noncompliant with the Treaty of Babel + */ + +#define FORMAT advsys +#define HOME_PAGE "http://www.ifarchive.org/if-archive/programming/advsys/" +#define FORMAT_EXT ".dat" +#define NO_METADATA +#define NO_COVER + +#include "treaty_builder.h" +#include +#include + +/* IFIDs for AdvSys are formed by prepending ADVSYS- to the default + MD5 ifid +*/ +static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) +{ + /* This line suppresses a warning from the borland compiler */ + if (story_file || extent) { } + ASSERT_OUTPUT_SIZE(8); + strcpy(output,"ADVSYS-"); + return INCOMPLETE_REPLY_RV; + +} + +/* The Advsys claim algorithm: bytes 2-8 of the file contain the + text "ADVSYS", unobfuscated in the following way: + 30 is added to each byte, then the bits are reversed +*/ +static int32 claim_story_file(void *story_file, int32 extent) +{ + char buf[7]; + int i; + if (extent >=8) + { + for(i=0;i<6;i++) + buf[i]=~(((char *)story_file)[i+2]+30); + buf[6]=0; + if (strcmp(buf,"ADVSYS")==0) return VALID_STORY_FILE_RV; + } + return INVALID_STORY_FILE_RV; +} diff --git a/babel/agt.c b/babel/agt.c new file mode 100644 index 0000000..66b6575 --- /dev/null +++ b/babel/agt.c @@ -0,0 +1,59 @@ +/* agt.c Treaty of Babel module for AGX-encapsulated AGT files + * 2006 By L. Ross Raszewski + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file + * may render it noncompliant with the Treaty of Babel + */ + +#define FORMAT agt +#define HOME_PAGE "http://www.ifarchive.org/indexes/if-archiveXprogrammingXagt" +#define FORMAT_EXT ".agx" +#define NO_METADATA +#define NO_COVER + +#include "treaty_builder.h" +#include +#include + + +static char AGX_MAGIC[4] = { 0x58, 0xC7, 0xC1, 0x51 }; + +/* Helper functions to unencode integers from AGT source */ +static int32 read_agt_short(unsigned char *sf) +{ + return sf[0] | (int32) sf[1]<<8; +} +static int32 read_agt_int(unsigned char *sf) +{ + return (read_agt_short(sf+2) << 16) | read_agt_short(sf); + +} + +static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) +{ + int32 l, game_version, game_sig; + unsigned char *sf=(unsigned char *)story_file; + + /* Read the position of the game desciption block */ + l=read_agt_int(sf+32); + if (extent +#include + +static int32 read_alan_int(unsigned char *from) +{ + return ((unsigned long int) from[3])| ((unsigned long int)from[2] << 8) | + ((unsigned long int) from[1]<<16)| ((unsigned long int)from[0] << 24); +} +static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) +{ + + if (story_file || extent) { } + ASSERT_OUTPUT_SIZE(6); + strcpy(output,"ALAN-"); + return INCOMPLETE_REPLY_RV; +} +/* + The claim algorithm for Alan files is: + * For Alan 3, check for the magic word + * load the file length in blocks + * check that the file length is correct + * For alan 2, each word between byte address 24 and 81 is a + word address within the file, so check that they're all within + the file + * Locate the checksum and verify that it is correct +*/ +static int32 claim_story_file(void *story_file, int32 extent) +{ + unsigned char *sf = (unsigned char *) story_file; + int32 bf, i, crc=0; + if (extent < 160) return INVALID_STORY_FILE_RV; + if (memcmp(sf,"ALAN",4)) + { /* Identify Alan 2.x */ + bf=read_alan_int(sf+4); + if (bf > extent/4) return INVALID_STORY_FILE_RV; + for (i=24;i<81;i+=4) + if (read_alan_int(sf+i) > extent/4) return INVALID_STORY_FILE_RV; + for (i=160;i<(bf*4);i++) + crc+=sf[i]; + if (crc!=read_alan_int(sf+152)) return INVALID_STORY_FILE_RV; + return VALID_STORY_FILE_RV; + } + else + { /* Identify Alan 3 */ + bf=read_alan_int(sf+12); + if (bf > (extent/4)) return INVALID_STORY_FILE_RV; + for (i=184;i<(bf*4);i++) + crc+=sf[i]; + if (crc!=read_alan_int(sf+176)) return INVALID_STORY_FILE_RV; + + } + return INVALID_STORY_FILE_RV; +} diff --git a/babel/babel b/babel/babel new file mode 100644 index 0000000..1808ec1 Binary files /dev/null and b/babel/babel differ diff --git a/babel/babel-makefile b/babel/babel-makefile new file mode 100644 index 0000000..1b31887 --- /dev/null +++ b/babel/babel-makefile @@ -0,0 +1,75 @@ +# provisional makefile for babel +# +# Note that to compile babel, it is necessary only to compile all the .c +# files in this distribution and link them. +# +# This makefile is provided purely as a convenience. +# +# The following targets are available: +# babel: make babel +# babel.lib: make babel handler library (for Borland) +# ifiction.lib: make babel ifiction library (for Borland) +# babel.a: make babel handler library (for gcc) +# ifiction.a: make babel ifiction library (for gcc) +# dist: make babel.zip, the babel source distribution +# +# Note that this is a GNU makefile, and may not work with other makes +# +# Comment/uncomment the following lines to make the program work + +#CC=bcc32 +#OBJ=.obj +#BABEL_LIB=babel.lib +#IFICTION_LIB=ifiction.lib +#BABEL_FLIB=babel_functions.lib +#OUTPUT_BABEL= + +CC=gcc -g +OBJ=.o +BABEL_LIB=babel.a +BABEL_FLIB=babel_functions.a +IFICTION_LIB=ifiction.a +OUTPUT_BABEL=-o babel + +treaty_objs = zcode${OBJ} magscrolls${OBJ} blorb${OBJ} glulx${OBJ} hugo${OBJ} agt${OBJ} level9${OBJ} executable${OBJ} advsys${OBJ} tads${OBJ} tads2${OBJ} tads3${OBJ} adrift${OBJ} alan${OBJ} +bh_objs = babel_handler${OBJ} register${OBJ} misc${OBJ} md5${OBJ} ${treaty_objs} +ifiction_objs = ifiction${OBJ} register_ifiction${OBJ} +babel_functions = babel_story_functions${OBJ} babel_ifiction_functions${OBJ} babel_multi_functions${OBJ} +babel_objs = babel${OBJ} $(BABEL_FLIB) $(IFICTION_LIB) $(BABEL_LIB) + +babel: ${babel_objs} + ${CC} ${OUTPUT_BABEL} ${babel_objs} + +%${OBJ} : %.c + ${CC} -c $^ + +register${OBJ}: modules.h + +babel.lib: ${foreach dep,${bh_objs},${dep}.bl} + +ifiction.lib: ${foreach dep,${ifiction_objs},${dep}.il} + +babel_functions.lib: ${foreach dep,${babel_functions},${dep}.fl} + +%.obj.bl: %.obj + tlib babel.lib +-$^ + echo made > $@ + +%.obj.il: %.obj + tlib ifiction.lib +-$^ + echo made > $@ +%.obj.fl: %.obj + tlib babel_functions.lib +-$^ + echo made > $@ + +babel.a: $(bh_objs) + ar -r babel.a $^ + +ifiction.a: $(ifiction_objs) + ar -r ifiction.a $^ + +babel_functions.a: $(babel_functions) + ar -r babel_functions.a $^ + +dist: + cut -c0-31 MANIFEST | zip babel.zip -@ diff --git a/babel/babel.a b/babel/babel.a new file mode 100644 index 0000000..142fb78 Binary files /dev/null and b/babel/babel.a differ diff --git a/babel/babel.c b/babel/babel.c new file mode 100644 index 0000000..5bb7213 --- /dev/null +++ b/babel/babel.c @@ -0,0 +1,248 @@ +/* babel.c The babel command line program + * (c) 2006 By L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends upon misc.c and babel.h + * + * This file exports one variable: char *rv, which points to the file name + * for an ifiction file. This is used only by babel_ifiction_verify + */ + +#include "babel.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +int chdir(const char *); +char *getcwd(char *, int); +#ifdef __cplusplus +} +#endif + +char *fn; + +/* checked malloc function */ +void *my_malloc(int, char *); + +/* babel performs several fundamental operations, which are specified + by command-line objects. Each of these functions corresponds to + a story function (defined in babel_story_functions.c) or an + ifiction function (defined in babel_ifiction_functions.c) or both. + These are the types of those functions. +*/ + +typedef void (*story_function)(void); +typedef void (*ifiction_function)(char *); +typedef void (*multi_function)(char **, char *, int); +/* This structure tells babel what to do with its command line arguments. + if either of story or ifiction are NULL, babel considers this command line + option inappropriate for that type of file. +*/ +struct function_handler { + char *function; /* the textual command line option */ + story_function story; /* handler for story files */ + ifiction_function ifiction; /* handler for ifiction files */ + char *desc; /* Textual description for help text */ + }; + +struct multi_handler { + char *function; + char *argnames; + multi_function handler; + int nargsm; + int nargsx; + char *desc; + }; +/* This is an array of function_handler objects which specify the legal + arguments. It is terminated by a function_handler with a NULL function + */ +static struct function_handler functions[] = { + { "-ifid", babel_story_ifid, babel_ifiction_ifid, "Deduce IFID"}, + { "-format", babel_story_format, NULL, "Deduce story format" }, + { "-ifiction", babel_story_ifiction, NULL, "Extract iFiction file" }, + { "-meta", babel_story_meta, NULL, "Print story metadata" }, + { "-identify", babel_story_identify, NULL, "Describe story file" }, + { "-cover", babel_story_cover, NULL, "Extract cover art" }, + { "-story", babel_story_story, NULL, "Extract story file (ie. from a blorb)" }, + { "-verify", NULL, babel_ifiction_verify, "Verify integrity of iFiction file" }, + { "-lint", NULL, babel_ifiction_lint, "Verify style of iFiction file" }, + { "-fish", babel_story_fish, babel_ifiction_fish, "Extract all iFiction and cover art"}, + { "-unblorb", babel_story_unblorb, NULL, "As -fish, but also extract story files"}, + { NULL, NULL, NULL } + }; +static struct multi_handler multifuncs[] = { + { "-blorb", " []", babel_multi_blorb, 2, 3, "Bundle story file and (sparse) iFiction into blorb" }, + { "-blorbs", " []", babel_multi_blorb1, 2, 3, "Bundle story file and (sparse) iFiction into sensibly-named blorb" }, + { "-complete", " ", babel_multi_complete, 2, 2, "Create complete iFiction file from sparse iFiction" }, + { NULL, NULL, NULL, 0, 0, NULL } +}; + +int main(int argc, char **argv) +{ + char *todir="."; + char cwd[512]; + int ok=1,i, l, ll; + FILE *f; + char *md=NULL; + /* Set the input filename. Note that if this is invalid, babel should + abort before anyone notices + */ + fn=argv[2]; + + if (argc < 3) ok=0; + /* Detect the presence of the "-to " argument. + */ + if (ok && argc >=5 && strcmp(argv[argc-2], "-to")==0) + { + todir=argv[argc-1]; + argc-=2; + } + if (ok) for(i=0;multifuncs[i].function;i++) + if (strcmp(argv[1],multifuncs[i].function)==0 && + argc>= multifuncs[i].nargsm+2 && + argc <= multifuncs[i].nargsx+2) + { + + multifuncs[i].handler(argv+2, todir, argc-2); + exit(0); + } + + if (argc!=3) ok=0; + + /* Find the apropriate function_handler */ + if (ok) { + for(i=0;functions[i].function && strcmp(functions[i].function,argv[1]);i++); + if (!functions[i].function) ok=0; + else if (strcmp(fn,"-")) { + f=fopen(argv[2],"r"); + if (!f) ok=0; + } + } + + /* Print usage error if anything has gone wrong */ + if (!ok) + { + printf("%s: Treaty of Babel Analysis Tool (%s, %s)\n" + "Usage:\n", argv[0],BABEL_VERSION, TREATY_COMPLIANCE); + for(i=0;functions[i].function;i++) + { + if (functions[i].story) + printf(" babel %s \n",functions[i].function); + if (functions[i].ifiction) + printf(" babel %s \n",functions[i].function); + printf(" %s\n",functions[i].desc); + } + for(i=0;multifuncs[i].function;i++) + { + printf("babel %s %s\n %s\n", + multifuncs[i].function, + multifuncs[i].argnames, + multifuncs[i].desc); + } + + printf ("\nFor functions which extract files, add \"-to \" to the command\n" + "to set the output directory.\n" + "The input file can be specified as \"-\" to read from standard input\n" + "(This may only work for .iFiction files)\n"); + return 1; + } + + /* For story files, we end up reading the file in twice. This + is unfortunate, but unavoidable, since we want to be all + cross-platformy, so the first time we read it in, we + do the read in text mode, and the second time, we do it in binary + mode, and there are platforms where this makes a difference. + */ + ll=0; + if (strcmp(fn,"-")) + { + fseek(f,0,SEEK_END); + l=ftell(f)+1; + fseek(f,0,SEEK_SET); + md=(char *)my_malloc(l,"Input file buffer"); + fread(md,1,l-1,f); + md[l-1]=0; + } + else + while(!feof(stdin)) + { + char *tt, mdb[1024]; + int ii; + ii=fread(mdb,1,1024,stdin); + tt=(char *)my_malloc(ll+ii,"file buffer"); + if (md) { memcpy(tt,md,ll); free(md); } + memcpy(tt+ll,mdb,ii); + md=tt; + ll+=ii; + if (ii<1024) break; + } + + + if (strstr(md,""); + if (pp) *(pp+10)=0; + getcwd(cwd,512); + chdir(todir); + l=0; + if (functions[i].ifiction) + functions[i].ifiction(md); + else + fprintf(stderr,"Error: option %s is not valid for iFiction files\n", + argv[1]); + chdir(cwd); + } + + if (strcmp(fn,"-")) + { + free(md); + fclose(f); + } + if (l) + { /* Appears to be a story */ + char *lt; + if (functions[i].story) + { + if (strcmp(fn,"-")) lt=babel_init(argv[2]); + else { lt=babel_init_raw(md,ll); + free(md); + } + + if (lt) + { + getcwd(cwd,512); + chdir(todir); + if (!babel_get_authoritative() && strcmp(argv[1],"-format")) + printf("Warning: Story format could not be positively identified. Guessing %s\n",lt); + functions[i].story(); + + chdir(cwd); + } + else if (strcmp(argv[1],"-ifid")==0) /* IFID is calculable for all files */ + { + babel_md5_ifid(cwd,512); + printf("IFID: %s\n",cwd); + } + else + fprintf(stderr,"Error: Did not recognize format of story file\n"); + babel_release(); + } + else + fprintf(stderr,"Error: option %s is not valid for story files\n", + argv[1]); + } + + return 0; +} diff --git a/babel/babel.h b/babel/babel.h new file mode 100644 index 0000000..4108e3d --- /dev/null +++ b/babel/babel.h @@ -0,0 +1,56 @@ +/* babel.h declarations for babel + * (c) 2006 By L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends upon treaty.h, babel_ifiction_functions.c, + * babel_story_functions.c, and babel_handler.c + * + */ + +#define BABEL_VERSION "0.2b" + +#include "treaty.h" +#include "babel_handler.h" +#include "ifiction.h" +/* Functions from babel_story_functions.c + * + * Each of these assumes that the story file has been loaded by babel_handler + * + * Each function babel_story_XXXX coresponds to the command line option -XXXX + */ +void babel_story_ifid(void); +void babel_story_cover(void); +void babel_story_ifiction(void); +void babel_story_meta(void); +void babel_story_fish(void); +void babel_story_format(void); +void babel_story_identify(void); +void babel_story_story(void); +void babel_story_unblorb(void); +/* Functions from babel_ifiction_functions.c + * + * as with babel_story_XXXX, but for metadata, which is handed in as the + * C string parameter + */ +void babel_ifiction_ifid(char *); +void babel_ifiction_verify(char *); +void babel_ifiction_fish(char *); +void babel_ifiction_lint(char *); + +/* Functions from babel_multi_functions.c + * + */ +void babel_multi_blorb(char **, char * , int); +void babel_multi_blorb1(char **, char * , int); +void babel_multi_complete(char **, char *, int); + +/* uncomment this line on platforms which limit extensions to 3 characters */ +/* #define THREE_LETTER_EXTENSIONS */ diff --git a/babel/babel_functions.a b/babel/babel_functions.a new file mode 100644 index 0000000..ea8a1f6 Binary files /dev/null and b/babel/babel_functions.a differ diff --git a/babel/babel_handler.c b/babel/babel_handler.c new file mode 100644 index 0000000..f7afb78 --- /dev/null +++ b/babel/babel_handler.c @@ -0,0 +1,366 @@ +/* babel_handler.c dispatches Treaty of Babel queries to the treaty modules + * (c) 2006 By L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends upon register.c, misc.c, babel.h, and treaty.h + * and L. Peter Deutsch's md5.c + * usage: + * char *babel_init(char *filename) + * Initializes babel to use the specified file. MUST be called before + * babel_treaty. Returns the human-readable name of the format + * or NULL if the format is not known. Do not call babel_treaty unless + * babel_init returned a nonzero value. + * The returned string will be the name of a babel format, possibly + * prefixed by "blorbed " to indicate that babel will process this file + * as a blorb. + * int32 babel_treaty(int32 selector, void *output, void *output_extent) + * Dispatches the call to the treaty handler for the currently loaded + * file. + * When processing a blorb, all treaty calls will be deflected to the + * special blorb handler. For the case of GET_STORY_FILE_IFID_SEL, + * The treaty handler for the underlying format will be called if an + * IFID is not found in the blorb resources. + * void babel_release() + * Frees all resources allocated during babel_init. + * You should do this even if babel_init returned NULL. + * After this is called, do not call babel_treaty until after + * another successful call to babel_init. + * char *babel_get_format() + * Returns the same value as the last call to babel_init (ie, the format name) + * int32 babel_md5_ifid(char *buffer, int extent); + * Generates an MD5 IFID from the loaded story. Returns zero if something + * went seriously wrong. + * + * If you wish to use babel in multiple threads, you must use the contextualized + * versions of the above functions. + * Each function above has a companion function whose name ends in _ctx (eg. + * "babel_treaty_ctx") which takes one additional argument. This argument is + * the babel context. A new context is returned by void *ctx=get_babel_ctx(), + * and should be released when finished by calling release_babel_ctx(ctx); + */ + + +#include "treaty.h" +#include +#include +#include +#include +#include "md5.h" + +void *my_malloc(int, char *); + +struct babel_handler +{ + TREATY treaty_handler; + TREATY treaty_backup; + void *story_file; + int32 story_file_extent; + void *story_file_blorbed; + int32 story_file_blorbed_extent; + char blorb_mode; + char *format_name; + char auth; +}; + +static struct babel_handler default_ctx; + +extern TREATY treaty_registry[]; +extern TREATY container_registry[]; + +static char *deeper_babel_init(char *story_name, void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + int i; + char *ext; + + static char buffer[TREATY_MINIMUM_EXTENT]; + int best_candidate; + char buffert[TREATY_MINIMUM_EXTENT]; + + if (story_name) + { + ext=strrchr(story_name,'.'); + if (ext) for(i=0;ext[i];i++) ext[i]=tolower(ext[i]); + } + else ext=NULL; + best_candidate=-1; + if (ext) /* pass 1: try best candidates */ + for(i=0;container_registry[i];i++) + if (container_registry[i](GET_FILE_EXTENSIONS_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT) >=0 && + strstr(buffer,ext) && + container_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0)>=NO_REPLY_RV) + break; + if (!ext || !container_registry[i]) /* pass 2: try all candidates */ + { + + for(i=0;container_registry[i];i++) + {int l=container_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0); + + if (l==VALID_STORY_FILE_RV) + break; + else if (l==NO_REPLY_RV && best_candidate < 0) best_candidate=i; + } +} + if (!container_registry[i] && best_candidate >=0) { bh->auth=0; i=best_candidate; } + if (container_registry[i]) + { + char buffer2[TREATY_MINIMUM_EXTENT]; + + bh->treaty_handler=container_registry[i]; + container_registry[i](GET_FORMAT_NAME_SEL,NULL,0,buffert,TREATY_MINIMUM_EXTENT); + bh->blorb_mode=1; + + bh->story_file_blorbed_extent=container_registry[i](CONTAINER_GET_STORY_EXTENT_SEL,bh->story_file,bh->story_file_extent,NULL,0); + if (bh->story_file_blorbed_extent>0) bh->story_file_blorbed=my_malloc(bh->story_file_blorbed_extent, "contained story file"); + if (bh->story_file_blorbed_extent<=0 || + container_registry[i](CONTAINER_GET_STORY_FORMAT_SEL,bh->story_file,bh->story_file_extent,buffer2,TREATY_MINIMUM_EXTENT)<0 || + container_registry[i](CONTAINER_GET_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,bh->story_file_blorbed,bh->story_file_blorbed_extent)<=0 + ) + return NULL; + + for(i=0;treaty_registry[i];i++) + if (treaty_registry[i](GET_FORMAT_NAME_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT)>=0 && + strcmp(buffer,buffer2)==0 && + treaty_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file_blorbed,bh->story_file_blorbed_extent,NULL,0)>=NO_REPLY_RV) + break; + if (!treaty_registry[i]) + return NULL; + bh->treaty_backup=treaty_registry[i]; + sprintf(buffer,"%sed %s",buffert,buffer2); + return buffer; + } + + bh->blorb_mode=0; + best_candidate=-1; + + if (ext) /* pass 1: try best candidates */ + for(i=0;treaty_registry[i];i++) + if (treaty_registry[i](GET_FILE_EXTENSIONS_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT) >=0 && + strstr(buffer,ext) && + treaty_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0)>=NO_REPLY_RV) + break; + if (!ext || !treaty_registry[i]) /* pass 2: try all candidates */ + { + + for(i=0;treaty_registry[i];i++) + {int l; + l=treaty_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0); + + if (l==VALID_STORY_FILE_RV) + break; + else if (l==NO_REPLY_RV && best_candidate < 0) best_candidate=i; + } + } + if (!treaty_registry[i]) + if (best_candidate>0) { i=best_candidate; bh->auth=0; } + else return NULL; + bh->treaty_handler=treaty_registry[i]; + + if (bh->treaty_handler(GET_FORMAT_NAME_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT)>=0) + return buffer; + return NULL; + + +} + +static char *deep_babel_init(char *story_name, void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + FILE *file; + + bh->treaty_handler=NULL; + bh->treaty_backup=NULL; + bh->story_file=NULL; + bh->story_file_extent=0; + bh->story_file_blorbed=NULL; + bh->story_file_blorbed_extent=0; + bh->format_name=NULL; + file=fopen(story_name, "rb"); + if (!file) return NULL; + fseek(file,0,SEEK_END); + bh->story_file_extent=ftell(file); + fseek(file,0,SEEK_SET); + bh->auth=1; + bh->story_file=my_malloc(bh->story_file_extent,"story file storage"); + fread(bh->story_file,1,bh->story_file_extent,file); + fclose(file); + + return deeper_babel_init(story_name, bhp); +} + +char *babel_init_ctx(char *sf, void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + char *b; + b=deep_babel_init(sf,bh); + if (b) bh->format_name=strdup(b); + return b; +} +char *babel_init(char *sf) +{ + return babel_init_ctx(sf, &default_ctx); +} + +char *babel_init_raw_ctx(void *sf, int32 extent, void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + char *b; + bh->treaty_handler=NULL; + bh->treaty_backup=NULL; + bh->story_file=NULL; + bh->story_file_extent=0; + bh->story_file_blorbed=NULL; + bh->story_file_blorbed_extent=0; + bh->format_name=NULL; + bh->story_file_extent=extent; + bh->auth=1; + bh->story_file=my_malloc(bh->story_file_extent,"story file storage"); + memcpy(bh->story_file,sf,extent); + + b=deeper_babel_init(NULL, bhp); + if (b) bh->format_name=strdup(b); + return b; +} +char *babel_init_raw(void *sf, int32 extent) +{ + return babel_init_raw_ctx(sf, extent, &default_ctx); +} + +void babel_release_ctx(void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + if (bh->story_file) free(bh->story_file); + bh->story_file=NULL; + if (bh->story_file_blorbed) free(bh->story_file_blorbed); + bh->story_file_blorbed=NULL; + if (bh->format_name) free(bh->format_name); + bh->format_name=NULL; +} +void babel_release() +{ + babel_release_ctx(&default_ctx); +} +int32 babel_md5_ifid_ctx(char *buffer, int32 extent, void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + md5_state_t md5; + int i; + unsigned char ob[16]; + if (extent <33 || bh->story_file==NULL) + return 0; + md5_init(&md5); + md5_append(&md5,bh->story_file,bh->story_file_extent); + md5_finish(&md5,ob); + for(i=0;i<16;i++) + sprintf(buffer+(2*i),"%02X",ob[i]); + buffer[32]=0; + return 1; + +} +int32 babel_md5_ifid(char *buffer, int32 extent) +{ + return babel_md5_ifid_ctx(buffer, extent, + &default_ctx); +} + +int32 babel_treaty_ctx(int32 sel, void *output, int32 output_extent,void *bhp) +{ + int32 rv; + struct babel_handler *bh=(struct babel_handler *) bhp; + if (!(sel & TREATY_SELECTOR_INPUT) && bh->blorb_mode) + rv=bh->treaty_backup(sel,bh->story_file_blorbed,bh->story_file_blorbed_extent,output, output_extent); + else + { + rv=bh->treaty_handler(sel,bh->story_file,bh->story_file_extent,output,output_extent); + if ((!rv|| rv==UNAVAILABLE_RV) && bh->blorb_mode) + rv=bh->treaty_backup(sel,bh->story_file_blorbed,bh->story_file_blorbed_extent,output, output_extent); + } + if (!rv && sel==GET_STORY_FILE_IFID_SEL) + return babel_md5_ifid_ctx(output,output_extent, bh); + if (rv==INCOMPLETE_REPLY_RV && sel==GET_STORY_FILE_IFID_SEL) + return babel_md5_ifid_ctx((void *)((char *) output+strlen((char *)output)), + output_extent-strlen((char *)output), + bh); + + return rv; +} +int32 babel_treaty(int32 sel, void *output, int32 output_extent) +{ + return babel_treaty_ctx(sel, output, output_extent, &default_ctx); +} +char *babel_get_format_ctx(void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + return bh->format_name; +} +char *babel_get_format() +{ + return babel_get_format_ctx(&default_ctx); +} +void *get_babel_ctx() +{ + return my_malloc(sizeof(struct babel_handler), "babel handler context"); +} +void release_babel_ctx(void *b) +{ + free(b); +} + +int32 babel_get_length_ctx(void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + return bh->story_file_extent; +} +int32 babel_get_length() +{ + return babel_get_length_ctx(&default_ctx); +} + +int32 babel_get_authoritative_ctx(void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + return bh->auth; +} +int32 babel_get_authoritative() +{ + return babel_get_authoritative_ctx(&default_ctx); +} +void *babel_get_file_ctx(void *bhp) +{ + struct babel_handler *bh=(struct babel_handler *) bhp; + return bh->story_file; +} +void *babel_get_file() +{ + return babel_get_file_ctx(&default_ctx); +} + +int32 babel_get_story_length_ctx(void *ctx) +{ + struct babel_handler *bh=(struct babel_handler *) ctx; + if (bh->blorb_mode) return bh->story_file_blorbed_extent; + return bh->story_file_extent; +} +int32 babel_get_story_length() +{ + + return babel_get_story_length_ctx(&default_ctx); +} +void *babel_get_story_file_ctx(void *ctx) +{ + struct babel_handler *bh=(struct babel_handler *) ctx; + if (bh->blorb_mode) return bh->story_file_blorbed; + return bh->story_file; +} +void *babel_get_story_file() +{ + return babel_get_story_file_ctx(&default_ctx); +} diff --git a/babel/babel_handler.h b/babel/babel_handler.h new file mode 100644 index 0000000..a01194a --- /dev/null +++ b/babel/babel_handler.h @@ -0,0 +1,65 @@ +/* babel_handler.h declarations for the babel handler API + * (c) 2006 By L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + */ + +#ifndef BABEL_HANDLER_H +#define BABEL_HANDLER_H + +#include "treaty.h" + +/* Functions from babel_handler.c */ +char *babel_init(char *filename); + /* initialize the babel handler */ +char *babel_init_raw(void *sf, int32 extent); + /* Initialize from loaded data */ +int32 babel_treaty(int32 selector, void *output, int32 output_extent); + /* Dispatch treaty calls */ +void babel_release(void); + /* Release babel_handler resources */ +char *babel_get_format(void); + /* return the format of the loaded file */ +int32 babel_md5_ifid(char *buffer, int32 extent); + /* IFID generator of last resort */ +int32 babel_get_length(void); + /* Fetch file length */ +int32 babel_get_story_length(void); + /* Fetch file length */ +int32 babel_get_authoritative(void); + /* Determine if babel handler has a good grasp on the format */ +void *babel_get_file(void); + /* Get loaded story file */ +void *babel_get_story_file(void); + /* Get loaded story file */ + +/* threadsafe versions of above */ +char *babel_init_ctx(char *filename, void *); + /* initialize the babel handler */ +int32 babel_treaty_ctx(int32 selector, void *output, int32 output_extent, void *); + /* Dispatch treaty calls */ +void babel_release_ctx(void *); + /* Release babel_handler resources */ +char *babel_get_format_ctx(void *); + /* return the format of the loaded file */ +int32 babel_md5_ifid_ctx(char *buffer, int extent, void *); + /* IFID generator of last resort */ +int32 babel_get_length_ctx(void *); +int32 babel_get_story_length_ctx(void *); +void *babel_get_file_ctx(void *bhp); +void *babel_get_story_ctx(void *bhp); +int32 babel_get_authoritative_ctx(void *bhp); +char *babel_init_raw_ctx(void *sf, int32 extent, void *bhp); +void *get_babel_ctx(void); +void release_babel_ctx(void *); + /* get and release babel contexts */ + +#endif diff --git a/babel/babel_ifiction_functions.c b/babel/babel_ifiction_functions.c new file mode 100644 index 0000000..e59adf6 --- /dev/null +++ b/babel/babel_ifiction_functions.c @@ -0,0 +1,168 @@ +/* babel_ifiction_functions.c babel top-level operations for ifiction + * (c) 2006 By L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends upon babel.c (for rv), babel.h, misc.c and ifiction.c + */ + +#include "babel.h" +#include +#include +#include +#include + +#ifndef THREE_LETTER_EXTENSIONS +#define IFICTION_EXT ".iFiction" +#else +#define IFICTION_EXT ".ifi" +#endif + +void *my_malloc(int, char *); + +struct IFiction_Info +{ + char ifid[256]; + int wmode; +}; + +static void write_story_to_disk(struct XMLTag *xtg, void *ctx) +{ + char *b, *ep; + char *begin, *end; + char buffer[TREATY_MINIMUM_EXTENT]; + int32 l, j; + if (ctx) { } + + if (strcmp(xtg->tag,"story")==0) + { + begin=xtg->begin; + end=xtg->end; + l=end-begin+1; + b=(char *)my_malloc(l,"XML buffer"); + memcpy(b,begin,l-1); + b[l]=0; + j=ifiction_get_IFID(b,buffer,TREATY_MINIMUM_EXTENT); + if (!j) + { + fprintf(stderr,"No IFID found for this story\n"); + free(b); + return; + } + ep=strtok(buffer,","); + while(ep) + { + char buf2[256]; + FILE *f; + sprintf(buf2,"%s%s",ep,IFICTION_EXT); + f=fopen(buf2,"w"); + + if (!f || + fputs("\n" + "" + "\n" + " ", + f)==EOF || + fputs(b,f)==EOF || + fputs("/\n\n",f)==EOF + ) + { + fprintf(stderr,"Error writing to file %s\n",buf2); + } else + printf("Extracted %s\n",buf2); + if (f) fclose(f); + + ep=strtok(NULL,","); + } + + free(b); + } +} + +void babel_ifiction_ifid(char *md) +{ + char output[TREATY_MINIMUM_EXTENT]; + int i; + char *ep; + i=ifiction_get_IFID(md,output,TREATY_MINIMUM_EXTENT); + if (!i) + + { + fprintf(stderr,"Error: No IFIDs found in iFiction file\n"); + return; + } + ep=strtok(output,","); + while(ep) + { + printf("IFID: %s\n",ep); + ep=strtok(NULL,","); + } + +} + +static char isok; + +static void examine_tag(struct XMLTag *xtg, void *ctx) +{ + struct IFiction_Info *xti=(struct IFiction_Info *)ctx; + + if (strcmp("ifid",xtg->tag)==0 && strcmp(xti->ifid,"UNKNOWN")==0) + { + memcpy(xti->ifid,xtg->begin,xtg->end-xtg->begin); + xti->ifid[xtg->end-xtg->begin]=0; + } + +} +static void verify_eh(char *e, void *ctx) +{ + if (*((int *)ctx) < 0) return; + if (*((int *)ctx) || strncmp(e,"Warning",7)) + { isok=0; + fprintf(stderr, "%s\n",e); + } +} + + + +void babel_ifiction_fish(char *md) +{ + int i=-1; + ifiction_parse(md,write_story_to_disk,NULL,verify_eh,&i); +} + +void deep_ifiction_verify(char *md, int f) +{ + struct IFiction_Info ii; + int i=0; + ii.wmode=0; + isok=1; + strcpy(ii.ifid,"UNKNOWN"); + ifiction_parse(md,examine_tag,&ii,verify_eh,&i); + if (f&& isok) printf("Verified %s\n",ii.ifid); +} +void babel_ifiction_verify(char *md) +{ + deep_ifiction_verify(md,1); + +} + + +void babel_ifiction_lint(char *md) +{ + struct IFiction_Info ii; + int i=1; + ii.wmode=1; + isok=1; + strcpy(ii.ifid,"UNKNOWN"); + ifiction_parse(md,examine_tag,&ii,verify_eh,&i); + if (isok) printf("%s conforms to iFiction style guidelines\n",ii.ifid); +} + + diff --git a/babel/babel_multi_functions.c b/babel/babel_multi_functions.c new file mode 100644 index 0000000..cd867c6 --- /dev/null +++ b/babel/babel_multi_functions.c @@ -0,0 +1,312 @@ +#include "babel.h" + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +int chdir(const char *); +char *getcwd(char *, int); +#ifdef __cplusplus +} +#endif + +void deep_ifiction_verify(char *md, int f); +void * my_malloc(int32, char *); +char *blorb_chunk_for_name(char *name); +#ifndef THREE_LETTER_EXTENSIONS +static char *ext_table[] = { "zcode", ".zblorb", + "glulx", ".gblorb", + NULL, NULL + }; +#else +static char *ext_table[] = { "zcode", ".zlb", + "glulx", ".glb", + NULL, NULL + }; + +#endif +char *blorb_ext_for_name(char *fmt) +{ + int i; + for(i=0;ext_table[i];i+=2) + if (strcmp(ext_table[i],fmt)==0) return ext_table[i+1]; +#ifndef THREE_LETTER_EXTENSIONS + return ".blorb"; +#else + return ".blb"; +#endif +} + +char *deep_complete_ifiction(char *fn, char *ifid, char *format) +{ + FILE *f; + int32 i; + char *md; + char *id, *idp; + char *idb; + f=fopen(fn,"r"); + if (!f) { fprintf(stderr,"Error: Can not open file %s\n",fn); + return NULL; + } + fseek(f,0,SEEK_END); + i=ftell(f); + fseek(f,0,SEEK_SET); + md=(char *) my_malloc(i+1,"Metadata buffer"); + fread(md,1,i,f); + md[i]=0; + id=strstr(md,""); + if (id) *(id+10)=0; + fclose(f); + id=strdup(ifid); + idp=strtok(id,","); + /* Find the identification chunk */ + { + char *bp, *ep; + bp=strstr(md,""); + if (!bp) + { + idb=(char *)my_malloc(TREATY_MINIMUM_EXTENT+128,"ident buffer"); + sprintf(idb,"%s\n", format); + } + else + { + int ii; + ep=strstr(bp,""); + idb=(char *)my_malloc(TREATY_MINIMUM_EXTENT+128+(ep-bp),"ident buffer"); + for(ii=16;bp+ii"); + if (bp) + if (memcmp(bp+8,format,strlen(format))) + fprintf(stderr,"Error: Format in sparse .iFiction does not match story\n"); + + } + + } + /* Insert the new ifids */ + while(idp) + { + char bfr[TREATY_MINIMUM_EXTENT]; + sprintf(bfr,"%s",idp); + if (!strstr(idb,bfr)) { strcat(idb,bfr); strcat(idb,"\n"); } + idp=strtok(NULL,","); + + } + free(id); + idp=(char *) my_malloc(strlen(md)+strlen(idb)+64, "Output metadata"); +/* printf("%d bytes for metadata\n",strlen(md)+strlen(idb)+64);*/ + id=strstr(md,""); + id[0]=0; + id+=7; + strcpy(idp,md); + strcat(idp,"\n \n"); + strcat(idp,idb); + free(idb); + strcat(idp," \n"); + strcat(idp,id); + free(md); + md=idp; + deep_ifiction_verify(md, 0); + return md; +} + +void write_int(int32 i, FILE *f) +{ + char bf[4]; + bf[0]=(((unsigned) i) >> 24) & 0xFF; + bf[1]=(((unsigned) i) >> 16) & 0xFF; + bf[2]=(((unsigned) i) >> 8) & 0xFF; + bf[3]=(((unsigned) i)) & 0xFF; + fwrite(bf,1,4,f); +} +static void _babel_multi_blorb(char *outfile, char **args, char *todir , int argc) +{ + int32 total, storyl, coverl, i; + char buffer[TREATY_MINIMUM_EXTENT+10]; + char b2[TREATY_MINIMUM_EXTENT]; + + char cwd[512]; + char *cover, *md, *cvrf, *ep; + + FILE *f, *c; + if (argc!=2 && argc !=3) + { + fprintf(stderr,"Invalid usage\n"); + return; + } + if (!babel_init(args[0])) + { + fprintf(stderr,"Error: Could not determine the format of file %s\n",args[0]); + return; + } + if (babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT)<=0 || + babel_treaty(GET_FORMAT_NAME_SEL,b2,TREATY_MINIMUM_EXTENT)<0 + ) + { + fprintf(stderr,"Error: Could not deduce an IFID for file %s\n",args[0]); + return; + } + if (babel_get_length() != babel_get_story_length()) + { + fprintf(stderr,"Warning: Story file will be extacted from container before blorbing\n"); + } +/* printf("Completing ifiction\n");*/ + md=deep_complete_ifiction(args[1],buffer,b2); +/* printf("Ifiction is %d bytes long\n",strlen(md));*/ + ep=strchr(buffer,','); + if (ep) *ep=0; + if (outfile) + strcpy(buffer,outfile); + strcat(buffer,blorb_ext_for_name(b2)); + getcwd(cwd,512); + chdir(todir); + f=fopen(buffer,"wb"); + chdir(cwd); + if (!f) + { + fprintf(stderr,"Error: Error writing to file %s\n",buffer); + return; + } + storyl=babel_get_story_length(); + total=storyl + (storyl%2) + 36; + if (md) total+=8+strlen(md)+strlen(md)%2; + if (argc==3) + { + c=fopen(args[2],"rb"); + if (c) + { + fseek(c,0,SEEK_END); + coverl=ftell(c); + if (coverl > 5){ + + cover=(char *) my_malloc(coverl+2,"Cover art buffer"); + fseek(c,0,SEEK_SET); + fread(cover,1,coverl,c); + if (memcmp(cover+1,"PNG",3)==0) cvrf="PNG "; + else cvrf="JPEG"; + total += 32+coverl + (coverl%2); + } + else argc=2; + fclose(c); + } + else argc=2; + } +/* printf("Writing header\n;");*/ + fwrite("FORM",1,4,f); + write_int(total,f); +/* printf("Writing index\n;");*/ + fwrite("IFRSRIdx",1,8,f); + write_int(argc==3 ? 28:16,f); + write_int(argc==3 ? 2:1,f); +/* printf("Writing story\n;");*/ + fwrite("Exec", 1,4,f); + write_int(0,f); + write_int(argc==3 ? 48:36,f); + if (argc==3) + { +/* printf("Writing image\n;"); */ + fwrite("Pict", 1,4,f); + write_int(1,f); + write_int(56+storyl+(storyl%2),f); + } +/* printf("Invoking chunk for name %s\n",b2); */ + fwrite(blorb_chunk_for_name(b2),1,4,f); + write_int(storyl,f); +/* printf("Writing story data\n"); */ + fwrite(babel_get_story_file(),1,storyl,f); + if (storyl%2) fwrite("\0",1,1,f); + if (argc==3) + { +/* printf("Writing cover data header %s\n",cvrf); */ + fwrite(cvrf,1,4,f); +/* printf("Writing cover data size %d\n",coverl); */ + write_int(coverl,f); +/* printf("Writing cover data\n"); */ + fwrite(cover,1,coverl,f); + if (coverl%2) fwrite("\0",1,1,f); +/* printf("Done with cover\n");*/ +/* free(cover);*/ +/* printf("Writing frontispiece\n;");*/ + fwrite("Fspc\0\0\0\004\0\0\0\001",1,12,f); + } + + if (md) { +/* printf("Writing metadata\n;");*/ + fwrite("IFmd",1,4,f); + write_int(strlen(md),f); + fwrite(md,1,strlen(md),f); + if (strlen(md)%2) + fwrite("\0",1,1,f); + free(md); + } + + fclose(f); + printf("Created %s\n",buffer); + +} +void babel_multi_complete(char **args, char *todir, int argc) +{ + char buffer[TREATY_MINIMUM_EXTENT+10]; + char b2[TREATY_MINIMUM_EXTENT]; + char cwd[512]; + char *ep, *md; + FILE *f; + if (argc!=2) + { + fprintf(stderr,"Invalid usage\n"); + return; + } + if (!babel_init(args[0])) + { + fprintf(stderr,"Error: Could not determine the format of file %s\n",args[0]); + return; + } + if (babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT)<=0 + || babel_treaty(GET_FORMAT_NAME_SEL,b2,TREATY_MINIMUM_EXTENT)<0) + { + fprintf(stderr,"Error: Could not deduce an IFID for file %s\n",args[0]); + return; + } + md=deep_complete_ifiction(args[1],buffer, b2); + if (!md) return; + ep=strchr(buffer,','); + if (ep) *ep=0; + strcat(buffer,".iFiction"); + getcwd(cwd,512); + chdir(todir); + f=fopen(buffer,"w"); + chdir(cwd); + if (!f || !fputs(md,f)) + { + fprintf(stderr,"Error: Error writing to file %s\n",buffer); + return; + } + fclose(f); + free(md); + printf("Created %s\n",buffer); +} +void babel_multi_blorb(char **args, char *todir , int argc) +{ + _babel_multi_blorb(NULL,args,todir,argc); +} +void babel_multi_blorb1(char **args, char *todir , int argc) +{ + char *buf; + char *bb; + buf=(char *)my_malloc(strlen(args[0])+1,"blorb name buffer"); + strcpy(buf,args[0]); + bb=strrchr(buf,'.'); + if (bb) *bb=0; + _babel_multi_blorb(buf,args,todir,argc); + free(buf); + + +} diff --git a/babel/babel_story_functions.c b/babel/babel_story_functions.c new file mode 100644 index 0000000..b1cf0e9 --- /dev/null +++ b/babel/babel_story_functions.c @@ -0,0 +1,411 @@ +/* babel_story_functions.c babel top-level operations for story files + * (c) 2006 By L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends upon babel_handler.c, babel.h, and misc.c + */ + +#include "babel.h" +#include +#include +#include + +#ifndef THREE_LETTER_EXTENSIONS +#define IFICTION_EXT ".iFiction" +#else +#define IFICTION_EXT ".ifi" +#endif +void *my_malloc(int32, char *); + +void babel_story_ifid() +{ + char buffer[TREATY_MINIMUM_EXTENT]; + char *ep; + int i; + i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT); + ep=strtok(buffer, ","); + while(ep) + { + printf("IFID: %s\n",ep); + ep=strtok(NULL,","); + } + if (!i) + fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n"); + +} + + +void babel_story_format() +{ + char *b; + b=babel_get_format(); + if (!b) b="unknown"; + if (!babel_get_authoritative()) + printf("Format: %s (non-authoritative)\n",b); + else printf("Format: %s\n",b); +} + +static void deep_babel_ifiction(char stopped) +{ + char buffer[TREATY_MINIMUM_EXTENT]; + char *md; + char *ep; + int32 i; + FILE *f; + + if (stopped!=2) + { + i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT); + if (i==0 && !babel_md5_ifid(buffer, TREATY_MINIMUM_EXTENT)) + { + fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n"); + return; + } + + + ep=strtok(buffer, ","); + } + else ep="-"; + i=babel_treaty(GET_STORY_FILE_METADATA_EXTENT_SEL,NULL,0); + if (i<=0) + { + if (stopped) printf("No iFiction record for %s\n",buffer); + return; + } + md=(char *)my_malloc(i,"Metadata buffer"); + if (babel_treaty(GET_STORY_FILE_METADATA_SEL,md,i)<0) + { + fprintf(stderr,"A serious error occurred while retrieving metadata.\n"); + free(md); + return; + } + while(ep) + { + char epb[TREATY_MINIMUM_EXTENT+9]; + if (stopped!=2) + { + strcpy(epb,ep); + strcat(epb, IFICTION_EXT); + + f=fopen(epb,"w"); + } + else f=stdout; + + if (!f || fputs(md,f)==EOF) + fprintf(stderr,"A serious error occurred writing to disk.\n"); + else if (stopped!=2) printf("Extracted %s\n",epb); + if (f) fclose(f); + if (stopped) break; + ep=strtok(NULL,","); + } + free(md); +} + +void babel_story_ifiction() +{ + deep_babel_ifiction(1); +} +static char *get_jpeg_dim(void *img, int32 extent) +{ + unsigned char *dp=(unsigned char *) img; + unsigned char *ep=dp+extent; + static char buffer[256]; + unsigned int t1, t2, w, h; + + + t1=*(dp++); + t2=*(dp++); + if (t1!=0xff || t2!=0xD8 ) + { + return "(invalid)"; + } + + while(1) + { + if (dp>ep) return "(invalid)"; + for(t1=*(dp++);t1!=0xff;t1=*(dp++)) if (dp>ep) return "(invalid)"; + do { t1=*(dp++); if (dp>ep) return "(invalid 4)";} while (t1 == 0xff); + + if ((t1 & 0xF0) == 0xC0 && !(t1==0xC4 || t1==0xC8 || t1==0xCC)) + { + dp+=3; + if (dp>ep) return "(invalid)"; + h=*(dp++) << 8; + if (dp>ep) return "(invalid)"; + h|=*(dp++); + if (dp>ep) return "(invalid)"; + w=*(dp++) << 8; + if (dp>ep) return "(invalid)"; + w|=*(dp); + sprintf(buffer, "(%dx%d)",w,h); + return buffer; + } + else if (t1==0xD8 || t1==0xD9) + break; + else + { int l; + + if (dp>ep) return "(invalid)"; + l=*(dp++) << 8; + if (dp>ep) return "(invalid)"; + l|= *(dp++); + l-=2; + dp+=l; + if (dp>ep) return "(invalid)"; + } + } + return "(invalid)"; +} + +static int32 read_int(unsigned char *mem) +{ + int32 i4 = mem[0], + i3 = mem[1], + i2 = mem[2], + i1 = mem[3]; + return i1 | (i2<<8) | (i3<<16) | (i4<<24); +} + + +static char *get_png_dim(void *img, int32 extent) +{ + unsigned char *dp=(unsigned char *)img; + static char buffer[256]; + int32 w, h; + if (extent<33 || + !(dp[0]==137 && dp[1]==80 && dp[2]==78 && dp[3]==71 && + dp[4]==13 && dp[5] == 10 && dp[6] == 26 && dp[7]==10)|| + !(dp[12]=='I' && dp[13]=='H' && dp[14]=='D' && dp[15]=='R')) + return "(invalid)"; + w=read_int(dp+16); + h=read_int(dp+20); + sprintf(buffer,"(%dx%d)",w,h); + return buffer; +} +static char *get_image_dim(void *img, int32 extent, int fmt) +{ + if (fmt==JPEG_COVER_FORMAT) return get_jpeg_dim(img,extent); + else if (fmt==PNG_COVER_FORMAT) return get_png_dim(img, extent); + return "(unknown)"; + +} +static void deep_babel_cover(char stopped) +{ + char buffer[TREATY_MINIMUM_EXTENT]; + void *md; + char *ep; + char *ext; + char *dim; + int32 i,j; + FILE *f; + i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT); + if (i==0) + if (babel_md5_ifid(buffer, TREATY_MINIMUM_EXTENT)) + printf("IFID: %s\n",buffer); + else + { + fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n"); + return; + } + else + + ep=strtok(buffer, ","); + i=babel_treaty(GET_STORY_FILE_COVER_EXTENT_SEL,NULL,0); + j=babel_treaty(GET_STORY_FILE_COVER_FORMAT_SEL,NULL,0); + + if (i<=0 || j<=0) + { + if (stopped) printf("No cover art for %s\n",buffer); + return; + } + if (j==PNG_COVER_FORMAT) ext=".png"; + else if (j==JPEG_COVER_FORMAT) ext=".jpg"; + md=my_malloc(i,"Image buffer"); + if (babel_treaty(GET_STORY_FILE_COVER_SEL,md,i)<0) + { + fprintf(stderr,"A serious error occurred while retrieving cover art.\n"); + free(md); + return; + } + dim=get_image_dim(md,i,j); + while(ep) + { + char epb[TREATY_MINIMUM_EXTENT+9]; + strcpy(epb,ep); + strcat(epb, ext); + + f=fopen(epb,"wb"); + if (!f || fwrite(md,1,i,f)==EOF) + fprintf(stderr,"A serious error occurred writing to disk.\n"); + else printf("Extracted %s %s\n",epb, dim); + if (f) fclose(f); + if (stopped) break; + ep=strtok(NULL,","); + } + free(md); +} + +void babel_story_cover() +{ + deep_babel_cover(1); +} + +void babel_story_fish() +{ + deep_babel_ifiction(0); + deep_babel_cover(0); +} + +static char *get_biblio(void) +{ + int32 i; + char *md; + char *bib="No bibliographic data"; + char *bibb; char *bibe; + char *t; + static char buffer[TREATY_MINIMUM_EXTENT]; + + i=babel_treaty(GET_STORY_FILE_METADATA_EXTENT_SEL,NULL,0); + if (i<=0) return bib; + + md=(char *) my_malloc(i,"Metadata buffer"); + if (babel_treaty(GET_STORY_FILE_METADATA_SEL,md,i)<0) return bib; + + bibb=strstr(md,""); + if (!bibb) { free(md); return bib; } + bibe=strstr(bibb,""); + if (bibe) *bibe=0; + t=strstr(bibb,""); + if (t) + { + t+=7; + bibe=strstr(t,""); + if (bibe) + { + *bibe=0; + bib=buffer; + for(i=0;t[i];i++) if (t[i]<0x20 || t[i]>0x7e) t[i]='_'; + sprintf(buffer, "\"%s\" ",t); + *bibe='<'; + } + else strcpy(buffer," "); + } + t=strstr(bibb,""); + if (t) + { + t+=8; + bibe=strstr(t,""); + if (bibe) + { + bib=buffer; + *bibe=0; + for(i=0;t[i];i++) if (t[i]<0x20 || t[i]>0x7e) t[i]='_'; + strcat(buffer, "by "); + strcat(buffer, t); + *bibe='<'; + } + else strcat(buffer, ""); + } + free(md); + return bib; + +} +void babel_story_identify() +{ + int32 i, j, l; + char *b, *cf, *dim; + char buffer[TREATY_MINIMUM_EXTENT]; + + printf("%s\n",get_biblio()); + babel_story_ifid(); + b=babel_get_format(); + if (!b) b="unknown"; + l=babel_get_length() / 1024; + + + i=babel_treaty(GET_STORY_FILE_COVER_EXTENT_SEL,NULL,0); + j=babel_treaty(GET_STORY_FILE_COVER_FORMAT_SEL,NULL,0); + + if (i<=0 || j<=0) + { + cf="no cover"; + } + else + { + char *md=my_malloc(i,"Image buffer"); + if (babel_treaty(GET_STORY_FILE_COVER_SEL,md,i)<0) + { + cf="no cover"; + } + else + { + dim=get_image_dim(md,i,j)+1; + dim[strlen(dim)-1]=0; + if (j==JPEG_COVER_FORMAT) cf="jpeg"; + else if (j==PNG_COVER_FORMAT) cf="png"; + else cf="unknown format"; + sprintf(buffer,"cover %s %s",dim,cf); + cf=buffer; + } + } + printf("%s, %dk, %s\n",b, l,cf); +} + +void babel_story_meta() +{ + deep_babel_ifiction(2); +} + +void babel_story_story() +{ + int32 j,i; + void *p; + FILE *f; + char *ep; + char buffer[TREATY_MINIMUM_EXTENT+20]; + j=babel_get_story_length(); + p=babel_get_story_file(); + if (!j || !p) + { + fprintf(stderr,"A serious error occurred while retrieving the story file.\n"); + return; + } + + i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT); + if (i==0 && !babel_md5_ifid(buffer, TREATY_MINIMUM_EXTENT)) + { + fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n"); + return; + } + ep=strchr(buffer, ','); + if (!ep) ep=buffer+strlen(buffer); + *ep=0; + babel_treaty(GET_STORY_FILE_EXTENSION_SEL,ep,19); + f=fopen(buffer,"wb"); + if (!f || !fwrite(p,1,j,f)) + { + fprintf(stderr,"A serious error occurred writing to disk.\n"); + return; + } + fclose(f); + printf("Extracted %s\n",buffer); + + + +} + +void babel_story_unblorb() +{ + deep_babel_ifiction(1); + deep_babel_cover(1); + babel_story_story(); + +} diff --git a/babel/blorb.c b/babel/blorb.c new file mode 100644 index 0000000..ebb84e4 --- /dev/null +++ b/babel/blorb.c @@ -0,0 +1,245 @@ +/* blorb.c Babel interface to blorb files + * Copyright 2006 by L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends upon treaty_builder.h, misc.c and ifiction.c + * + * Header note: to add support for new executable chunk types, see + * TranslateExec. + * + * This file defines a Treaty of Babel compatable module for handling blorb + * files. However, blorb files are not themselves a babel format. This module + * is used internally by the babel program to handle blorbs. + * + * As a result, if GET_STORY_FILE_IFID_SEL returns NO_REPLY_RV, + * you should check the story file against the babel registry before resorting + * to the default IFID calculation. + * + */ +#define FORMAT blorb +#define HOME_PAGE "http://eblong.com/zarf/blorb" +#define FORMAT_EXT ".blorb,.blb,.zblorb,.zlb,.gblorb,.glb" +#define CONTAINER_FORMAT +#include "treaty_builder.h" +#include +#include + +extern TREATY treaty_registry[]; +/* The following is the translation table of Blorb chunk types to + babel formats. it is NULL-terminated. */ +static char *TranslateExec[] = { "ZCOD", "zcode", + "GLUL", "glulx", + "TAD2", "tads2", + "TAD3", "tads3", + NULL, NULL }; + +void *my_malloc(int32, char *); +int32 ifiction_get_IFID(char *, char *, int32); + +static int32 read_int(void *inp) +{ + unsigned char *mem=(unsigned char *)inp; + int32 i4 = mem[0], + i3 = mem[1], + i2 = mem[2], + i1 = mem[3]; + return i1 | (i2<<8) | (i3<<16) | (i4<<24); +} + + +static int32 blorb_get_chunk(void *blorb_file, int32 extent, char *id, int32 *begin, int32 *output_extent) +{ + int32 i=12, j; + while(i extent) return NO_REPLY_RV; + *begin=i+8; + return 1; + } + + j=read_int((char *)blorb_file+i+4); + if (j%2) j++; + i+=j+8; + + } + return NO_REPLY_RV; +} +static int32 blorb_get_resource(void *blorb_file, int32 extent, char *rid, int32 number, int32 *begin, int32 *output_extent) +{ + int32 ridx_len; + int32 i,j; + void *ridx; + if (blorb_get_chunk(blorb_file, extent,"RIdx",&i,&ridx_len)==NO_REPLY_RV) + return NO_REPLY_RV; + + ridx=(char *)blorb_file+i+4; + ridx_len=read_int((char *)blorb_file+i); + for(i=0;i +#include + +static char elfmagic[] = { 0x7f, 0x45, 0x4c, 0x46, 0 }; +static char javamagic[] = { 0xCA, 0xFE, 0xBA, 0xBE, 0 }; +static char amigamagic[] = { 0, 0, 3, 0xe7, 0 }; +static char machomagic[] = { 0xFE, 0xED, 0xFA, 0xCE, 0}; +struct exetype +{ + char *magic; + char *name; + int len; +}; +static struct exetype magic[]= { { "MZ", "MZ", 2 }, + { elfmagic, "ELF", 4 }, + { javamagic, "JAVA", 4 }, + { amigamagic, "AMIGA", 4 }, + { "#! ", "SCRIPT", 3 }, + { machomagic, "MACHO",4 }, + { "APPL", "MAC",4 }, + { NULL, NULL, 0 } }; + +static char *deduce_magic(void *sf, int32 extent) +{ + int i; + for(i=0;magic[i].magic;i++) + if (extent >= magic[i].len && memcmp(magic[i].magic,sf,magic[i].len)==0) + return magic[i].name; + return NULL; +} + +static int32 claim_story_file(void *sf, int32 extent) +{ + if (deduce_magic(sf,extent)) return VALID_STORY_FILE_RV; + return NO_REPLY_RV; +} +static int32 get_story_file_IFID(void *sf, int32 extent, char *output, int32 output_extent) +{ + char *o; + o=deduce_magic(sf,extent); + if (!o) return 0; + ASSERT_OUTPUT_SIZE((signed) strlen(o)+2); + strcpy(output,o); + strcat(output,"-"); + return INCOMPLETE_REPLY_RV; +} diff --git a/babel/glulx.c b/babel/glulx.c new file mode 100644 index 0000000..ea1b3c4 --- /dev/null +++ b/babel/glulx.c @@ -0,0 +1,81 @@ +/* glulx.c Treaty of Babel module for Glulx files + * 2006 By L. Ross Raszewski + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file + * may render it noncompliant with the Treaty of Babel + */ + +#define FORMAT glulx +#define HOME_PAGE "http://eblong.com/zarf/glulx" +#define FORMAT_EXT ".ulx" +#define NO_METADATA +#define NO_COVER + +#include "treaty_builder.h" +#include +#include + +static int32 read_int(unsigned char *mem) +{ + int32 i4 = mem[0], + i3 = mem[1], + i2 = mem[2], + i1 = mem[3]; + return i1 | (i2<<8) | (i3<<16) | (i4<<24); +} + + + +static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) +{ + int32 i,j, k; + char ser[7]; + char buffer[32]; + + + if (extent<256) return INVALID_STORY_FILE_RV; + for(i=0;i +#include + +static int32 get_story_file_IFID(void *s_file, int32 extent, char *output, int32 output_extent) +{ + + int32 i,j; + char ser[9]; + char buffer[32]; + char *story_file = (char *) s_file; + + + if (extent<0x0B) return INVALID_STORY_FILE_RV; + + for(i=0;i0x7e) return INVALID_STORY_FILE_RV; + for(i=0x0b;i<0x18;i+=2) + if (read_hugo_addx(sf+i) * scale > extent) return INVALID_STORY_FILE_RV; + + return VALID_STORY_FILE_RV; +} diff --git a/babel/ifiction.a b/babel/ifiction.a new file mode 100644 index 0000000..6dda84f Binary files /dev/null and b/babel/ifiction.a differ diff --git a/babel/ifiction.c b/babel/ifiction.c new file mode 100644 index 0000000..cb620e7 --- /dev/null +++ b/babel/ifiction.c @@ -0,0 +1,534 @@ +/* ifiction.c common babel interface for processing ifiction metadata + * (c) 2006 By L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends on treaty.h + * + * This file contains common routines for handling ifiction metadata strings + * + * int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent) + * does what the babel treaty function GET_STORY_FILE_IFID_SEL would do for ifiction + * + * void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx, + * IFErrorHandler error_handler, void *error_ctx) + * parses the given iFiction metadata. close_tag(struct XMLtag xtg, close_ctx) + * is called for each tag as it is closed, error_handler(char *error, error_ctx) + * is called each time a structural or logical error is found in the iFiction + * This is a very simple XML parser, and probably not as good as any "real" + * XML parser. Its only two benefits are that (1) it's really small, and (2) + * it strictly checks the ifiction record against the Treaty of Babel + * requirements + * + */ + +#include "ifiction.h" +#include +#include +#include +#include + +void *my_malloc(int, char *); +extern char *format_registry[]; + + +static int32 llp; +static char *lnlst; + +static char utfeol[3] = { 0xe2, 0x80, 0xa8 }; +static int32 getln(char *endp) +{ + for(;lnlst"); + if (!ifid_begin) return NO_REPLY_RV; + ifid_begin+=6; + + ifid_end=strstr(ifid_begin,""); + if (!ifid_end) return NO_REPLY_RV; + if (output_extent<=(ifid_end-ifid_begin)) return INVALID_USAGE_RV; + + memcpy(output,ifid_begin,ifid_end-ifid_begin); + + output[ifid_end-ifid_begin]=0; + + return ifid_end-metadata+7; +} + + +int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent) +{ + int32 j=0, k; + + while(*metadata) + { + if ((k=ifiction_get_first_IFID(metadata,output,output_extent)) <= 0) break; + j++; + metadata+=k; + output_extent-=strlen(output)+1; + output+=strlen(output); + *output=','; + output++; + } + if (*(output-1)==',') *(output-1)=0; + return j; +} + + +static char *leaf_tags[] = { "ifid", + "format", + "bafn", + "title", + "author", + "headline", + "firstpublished", + "genre", + "group", + "description", + "leafname", + "url", + "authoremail", + "height", + "width", + + NULL + }; +static char *one_per[] = { "identification", + "bibliographic", + "format", + "title", + "author", + "headline", + "firstpublished", + "genre", + "group", + "description", + "leafname", + "height", + "width", + "forgiveness", + "colophon", + NULL + }; + +static char *required[] = { + "cover", "height", + "cover", "width", + "cover", "format", + "resources", "auxiliary", + "auxiliary", "leafname", + "auxiliary", "description", + "ifiction", "story", + "story", "identification", + "story", "bibliographic", + "identification", "ifid", + "identification", "format", + "bibliographic", "title", + "bibliographic", "author", + "colophon", "generator", + "colophon", "originated", + NULL, NULL + }; +static char *zarfian[] = { + "Merciful", + "Polite", + "Tough", + "Nasty", + "Cruel", + NULL + }; + +struct ifiction_info { + int32 width; + int32 height; + int format; + }; +static void ifiction_validate_tag(struct XMLTag *xtg, struct ifiction_info *xti, IFErrorHandler err_h, void *ectx) +{ + int i; + char ebuf[512]; + struct XMLTag *parent=xtg->next; + if (parent) + { + for(i=0;leaf_tags[i];i++) + if (strcmp(parent->tag,leaf_tags[i])==0) + { + sprintf(ebuf, "Error: (line %d) Tag <%s> is not permitted within tag <%s>", + xtg->beginl,xtg->tag,parent->tag); + err_h(ebuf,ectx); + } + for(i=0;required[i];i+=2) + if (strcmp(required[i],parent->tag)==0 && strcmp(required[i+1],xtg->tag)==0) + parent->rocurrences[i]=1; + for(i=0;one_per[i];i++) + if (strcmp(one_per[i],xtg->tag)==0) + if (parent->occurences[i]) { + sprintf(ebuf,"Error: (line %d) Found more than one <%s> within <%s>",xtg->beginl,xtg->tag, + parent->tag); + err_h(ebuf,ectx); + } + else parent->occurences[i]=1; + } + for(i=0;required[i];i+=2) + if (strcmp(required[i],xtg->tag)==0 && !xtg->rocurrences[i]) + { + sprintf(ebuf,"Error: (line %d) Tag <%s> is required within <%s>",xtg->beginl, required[i+1],xtg->tag); + err_h(ebuf,ectx); + } + if (parent && strcmp(parent->tag,"identification")==0) + { + if (strcmp(xtg->tag,"format")==0) + { + int i; + for(i=0;format_registry[i];i++) if (memcmp(xtg->begin,format_registry[i],strlen(format_registry[i]))==0) break; + if (format_registry[i]) xti->format=i; + else + { + char bf[256]; + memcpy(bf,xtg->begin,xtg->end-xtg->begin); + bf[xtg->end-xtg->begin]=0; + xti->format=-1; + sprintf(ebuf,"Warning: (line %d) Unknown format %s.",xtg->beginl,bf); + err_h(ebuf,ectx); + } + } + } + if (parent && strcmp(parent->tag,"cover")==0) + { + if (strcmp(xtg->tag,"width")==0) + { + int i; + sscanf(xtg->begin,"%d",&i); + if (i<120) + { + sprintf(ebuf,"Warning: (line %d) Cover art width should not be less than 120.",xtg->beginl); + err_h(ebuf,ectx); + } + if (i>1200) + { + sprintf(ebuf,"Warning: (line %d) Cover art width should not exceed 1200.",xtg->beginl); + err_h(ebuf,ectx); + } + if (!xti->width) xti->width=i; + if (xti->height && (xti->width> 2 * xti->height || xti->height > 2 * xti->width)) + { + sprintf(ebuf,"Warning: (line %d) Cover art aspect ratio exceeds 2:1.",xtg->beginl); + err_h(ebuf,ectx); + } + + } + if (strcmp(xtg->tag,"height")==0) + { + int i; + sscanf(xtg->begin,"%d",&i); + if (i<120) + { + sprintf(ebuf,"Warning: (line %d) Cover art height should not be less than 120.",xtg->beginl); + err_h(ebuf,ectx); + } + if (i>1200) + { + sprintf(ebuf,"Warning: (line %d) Cover art height should not exceed 1200.",xtg->beginl); + err_h(ebuf,ectx); + } + if (!xti->height) xti->height=i; + if (xti->width && (xti->width> 2 * xti->height || xti->height > 2 * xti->width)) + { + sprintf(ebuf,"Warning: (line %d) Cover art aspect ratio exceeds 2:1.",xtg->beginl); + err_h(ebuf,ectx); + } + + } + if (strcmp(xtg->tag,"format")==0 && memcmp(xtg->begin,"jpg",3) && memcmp(xtg->begin,"png",3)) + { + sprintf(ebuf,"Warning: (line %d) should be one of: png, jpg.",xtg->beginl); + err_h(ebuf,ectx); + } + } + if (parent && strcmp(parent->tag,"bibliographic")==0) + { + char *p; + if (isspace(*xtg->begin)|| isspace(*(xtg->end-1))) + { + sprintf(ebuf,"Warning: (line %d) Extraneous spaces at beginning or end of tag <%s>.",xtg->beginl,xtg->tag); + err_h(ebuf,ectx); + } + for(p=xtg->begin;pend-1;p++) +/* Obsoleted by Revision 6 + if (isspace(*p) && isspace(*(p+1))) + { + sprintf(ebuf,"Warning: (line %d) Extraneous spaces found in tag <%s>.",xtg->beginl, xtg->tag); + err_h(ebuf,ectx); + } + else if (isspace(*p) && *p!=' ') + { + sprintf(ebuf,"Warning: (line %d) Improper whitespace character found in tag <%s>.",xtg->beginl, xtg->tag); + err_h(ebuf,ectx); + + } +*/ + if (strcmp(xtg->tag, "description") && xtg->end-xtg->begin > 240) + { + sprintf(ebuf,"Warning: (line %d) Tag <%s> length exceeds treaty guidelines",xtg->beginl, xtg->tag); + err_h(ebuf,ectx); + } + if (strcmp(xtg->tag, "description")==0 && xtg->end-xtg->begin > 2400) + { + sprintf(ebuf,"Warning: (line %d) Tag <%s> length exceeds treaty guidelines",xtg->beginl, xtg->tag); + err_h(ebuf,ectx); + } + if (strcmp(xtg->tag,"firstpublished")==0) + { + int l=xtg->end-xtg->begin; + if ((l!=4 && l!=10) || + (!isdigit(xtg->begin[0]) || + !isdigit(xtg->begin[1]) || + !isdigit(xtg->begin[2]) || + !isdigit(xtg->begin[3])) || + (l==10 && ( xtg->begin[4]!='-' || + xtg->begin[7]!='-' || + !isdigit(xtg->begin[5]) || + !isdigit(xtg->begin[6]) || + !(xtg->begin[5]=='0' || xtg->begin[5]=='1') || + !(xtg->begin[5]=='0' || xtg->begin[6]<='2') || + !isdigit(xtg->begin[8]) || + !isdigit(xtg->begin[9])))) + { + sprintf(ebuf,"Warning: (line %d) Tag <%s> should be format YYYY or YYYY-MM-DD",xtg->beginl, xtg->tag); + err_h(ebuf,ectx); + } + } + if (strcmp(xtg->tag,"seriesnumber")==0) + { + char *l; + if (*xtg->begin=='0' && xtg->end!=xtg->begin+1) + { + sprintf(ebuf,"Warning: (line %d) Tag <%s> should not use leading zeroes",xtg->beginl, xtg->tag); + err_h(ebuf,ectx); + } + + for(l=xtg->begin;lend;l++) if (!isdigit(*l)) + { + sprintf(ebuf,"Warning: (line %d) Tag <%s> should be a positive number",xtg->beginl, xtg->tag); + err_h(ebuf,ectx); + } + } + if (strcmp(xtg->tag,"forgiveness")==0) + { + int l; + for(l=0;zarfian[l];l++) if (memcmp(xtg->begin,zarfian[l],strlen(zarfian[l]))==0) break; + if (!zarfian[l]) + { + sprintf(ebuf,"Warning: (line %d) should be one of: Merciful, Polite, Tough, Cruel",xtg->beginl); + err_h(ebuf,ectx); + } + } + } + if (xti->format>0) + { + for(i=0;format_registry[i];i++) if (strcmp(xtg->tag,format_registry[i])==0) break; + if (format_registry[i] && xti->format !=i) + { + sprintf(ebuf,"Warning: (line %d) Found <%s> tag, but story is identified as %s.",xtg->beginl, xtg->tag, format_registry[xti->format]); + err_h(ebuf,ectx); + } + } + if (strcmp(xtg->tag,"story")==0) + { + xti->format=-1; + xti->width=0; + xti->height=0; + } + +} + + + +void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx, IFErrorHandler error_handler, void *error_ctx) +{ +char *xml, buffer[2400], *aep, *mda=md, ebuffer[512]; +struct XMLTag *parse=NULL, *xtg; +struct ifiction_info xti; +char BOM[3]={ 0xEF, 0xBB, 0xBF}; +xti.width=0; +xti.height=0; +xti.format=-1; +llp=1; +lnlst=md; + +while(*mda && isspace(*mda)) mda++; +if (memcmp(mda,BOM,3)==0) +{ mda+=3; + while(*mda && isspace(*mda)) mda++; +} + + +if (strncmp("",mda, + strlen("")) + && + strncmp("",mda, + strlen("")) + ) +{ + error_handler("Error: XML header not found.",error_ctx); + return; +} + +xml=strstr(md," not found",error_ctx); + return; + } +while(xml && *xml) +{ + char *bp, *ep, *tp; + while(*xml&&*xml!='<') xml++; + if (!*xml) break; + bp=xml; + tp=strchr(bp+1,'<'); + ep=strchr(bp+1,'>'); + if (!ep) break; + if (tp && tp < ep) + { xml=tp; continue; } + if (!tp) tp=ep+1; + if (bp[1]=='/') /* end tag */ + { + strncpy(buffer,bp+2,(ep-bp)-2); + buffer[(ep-bp)-2]=0; + if (parse && strcmp(buffer,parse->tag)==0) + { /* copasetic. Close the tag */ + xtg=parse; + parse=xtg->next; + xtg->end=ep-strlen(buffer)-2; + ifiction_validate_tag(xtg,&xti,error_handler, error_ctx); + close_tag(xtg,close_ctx); + free(xtg); + } + else + { + for(xtg=parse;xtg && strcmp(buffer,xtg->tag);xtg=xtg->next); + if (xtg) /* Intervening unclosed tags */ + { for(xtg=parse;xtg && strcmp(buffer,parse->tag);xtg=parse) + { + xtg->end=xml-1; + parse=xtg->next; + sprintf(ebuffer,"Error: (line %d) unclosed <%s> tag",xtg->beginl,xtg->tag); + error_handler(ebuffer,error_ctx); + ifiction_validate_tag(xtg,&xti,error_handler, error_ctx); + close_tag(xtg,close_ctx); + free(xtg); + } + xtg=parse; + if (xtg) + { + xtg->end=xml-1; + parse=xtg->next; + ifiction_validate_tag(xtg,&xti, error_handler, error_ctx); + close_tag(xtg,close_ctx); + free(xtg); + } + } + else + { + sprintf(ebuffer,"Error: (line %d) saw without <%s>",getln(xml), buffer,buffer); + error_handler(ebuffer,error_ctx); + } + } + + } + else if(*(ep-1)=='/' || bp[1]=='!') /* unterminated tag */ + { + /* Do nothing */ + } + else /* Terminated tag beginning */ + { + int i; + xtg=(struct XMLTag *)my_malloc(sizeof(struct XMLTag),"XML Tag"); + xtg->next=parse; + xtg->beginl=getln(bp); + for(i=0;bp[i+1]=='_' || bp[i+1]=='-' || isalnum(bp[i+1]);i++) + xtg->tag[i]=bp[i+1]; + if (i==0) + { xml=tp; + free(xtg); + continue; + } + parse=xtg; + parse->tag[i]=0; + strncpy(parse->fulltag,bp+1,ep-bp-1); + parse->fulltag[ep-bp-1]=0; + parse->begin=ep+1; + } + xml=tp; +} + while (parse) + { + xtg=parse; + xtg->end=aep-1; + parse=xtg->next; + sprintf(ebuffer,"Error: (line %d) Unclosed tag <%s>",xtg->beginl,xtg->tag); + ifiction_validate_tag(xtg,&xti,error_handler, error_ctx); + close_tag(xtg,close_ctx); + free(xtg); + } +} + +struct get_tag +{ + char *tag; + char *parent; + char *output; + char *target; +}; + +static void ifiction_null_eh(char *e, void *c) +{ + if (e || c) { } + +} + +static void ifiction_find_value(struct XMLTag *xtg, void *xti) +{ + struct get_tag *gt=(struct get_tag *)xti; + + if (gt->output && !gt->target) return; + if (gt->target && gt->output && strcmp(gt->output,gt->target)==0) { gt->target=NULL; free(gt->output); gt->output=NULL; } + if (((!xtg->next && !gt->parent) || (xtg->next && gt->parent && strcmp(xtg->next->tag,gt->parent)==0)) && + strcmp(xtg->tag,gt->tag)==0) + { + int32 l = xtg->end-xtg->begin; + + if (gt->output) free(gt->output); + gt->output=(char *)my_malloc(l+1, "ifiction tag buffer"); + memcpy(gt->output, xtg->begin, l); + gt->output[l]=0; + + } +} + + +char *ifiction_get_tag(char *md, char *p, char *t, char *from) +{ + struct get_tag gt; + gt.output=NULL; + gt.parent=p; + gt.tag=t; + gt.target=from; + ifiction_parse(md,ifiction_find_value,>,ifiction_null_eh,NULL); + if (gt.target){ if (gt.output) free(gt.output); return NULL; } + return gt.output; +} diff --git a/babel/ifiction.h b/babel/ifiction.h new file mode 100644 index 0000000..75ae946 --- /dev/null +++ b/babel/ifiction.h @@ -0,0 +1,45 @@ +/* ifiction.h declarations for the babel ifiction API + * (c) 2006 By L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + */ + +#ifndef IFICTION_H +#define IFICTION_H + +#include "treaty.h" + +/* Babel's notion of an XML tag */ +struct XMLTag +{ + int32 beginl; /* Beginning line number */ + char tag[256]; /* name of the tag */ + char fulltag[256]; /* Full text of the opening tag */ + char *begin; /* Points to the beginning of the tag's content */ + char *end; /* Points to the end of the tag's content. + setting *end=0 will turn begin into a string + containing the tag's content (But if you do this, you + should restore the original value of *end before + allowing control to return to the ifiction parser) */ + char occurences[256]; /* Tables used internally to find missing required tags */ + char rocurrences[256]; + struct XMLTag *next; /* The tag's parent */ + +}; + +typedef void (*IFCloseTag)(struct XMLTag *, void *); +typedef void (*IFErrorHandler)(char *, void *); + + +void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx, IFErrorHandler error_handler, void *error_ctx); +int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent); +char *ifiction_get_tag(char *md, char *p, char *t, char *from); +#endif diff --git a/babel/level9.c b/babel/level9.c new file mode 100644 index 0000000..de855b0 --- /dev/null +++ b/babel/level9.c @@ -0,0 +1,495 @@ +/* level9.c Treaty of Babel module for Level 9 files + * 2006 By L. Ross Raszewski + * + * Note that this module will handle both bare Level 9 A-Code and + * Spectrum .SNA snapshots. It will not handle compressed .Z80 images. + * + * The Level 9 identification algorithm is based in part on the algorithm + * used by Paul David Doherty's l9cut program. + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file + * may render it noncompliant with the Treaty of Babel + */ + +#define FORMAT level9 +#define HOME_PAGE "http://www.if-legends.org/~l9memorial/html/home.html" +#define FORMAT_EXT ".l9,.sna" +#define NO_METADATA +#define NO_COVER + +#include "treaty_builder.h" +#include +#include +#include + +struct l9rec { + int32 length; + unsigned char chk; + char *ifid; +}; + + +static struct l9rec l9_registry[] = { + { 0x3a31, 0xe5, "LEVEL9-001-1" }, + { 0x8333, 0xb7, "LEVEL9-001-1" }, + { 0x7c6f, 0x0f, "LEVEL9-001-1" }, + { 0x72fa, 0x8b, "LEVEL9-001-1" }, + { 0x38dd, 0x31, "LEVEL9-001-A" }, + { 0x39c0, 0x44, "LEVEL9-001-B" }, + { 0x3a12, 0x8f, "LEVEL9-001-C" }, + { 0x37f1, 0x77, "LEVEL9-001-2" }, + { 0x844d, 0x50, "LEVEL9-001-2" }, + { 0x738e, 0x5b, "LEVEL9-001-2" }, + { 0x3900, 0x1c, "LEVEL9-001-3" }, + { 0x8251, 0x5f, "LEVEL9-001-3" }, + { 0x7375, 0xe5, "LEVEL9-001-3" }, + { 0x3910, 0xac, "LEVEL9-001-4" }, + { 0x7a78, 0x5e, "LEVEL9-001-4" }, + { 0x78d5, 0xe3, "LEVEL9-001-4" }, + { 0x3ad6, 0xa7, "LEVEL9-001-5" }, + { 0x38a5, 0x0f, "LEVEL9-001-6" }, + { 0x361e, 0x7e, "LEVEL9-001-7" }, + { 0x3934, 0x75, "LEVEL9-001-8" }, + { 0x3511, 0xcc, "LEVEL9-001-9" }, + { 0x593a, 0xaf, "LEVEL9-002-1" }, + { 0x7931, 0xb9, "LEVEL9-002-1" }, + { 0x6841, 0x4a, "LEVEL9-002-1" }, + { 0x57e6, 0x8a, "LEVEL9-002-2" }, + { 0x7cdf, 0xa5, "LEVEL9-002-2" }, + { 0x6bc0, 0x62, "LEVEL9-002-2" }, + { 0x5819, 0xcd, "LEVEL9-002-3" }, + { 0x7a0c, 0x97, "LEVEL9-002-3" }, + { 0x692c, 0x21, "LEVEL9-002-3" }, + { 0x579b, 0xad, "LEVEL9-002-4" }, + { 0x7883, 0xe2, "LEVEL9-002-4" }, + { 0x670a, 0x94, "LEVEL9-002-4" }, + { 0x5323, 0xb7, "LEVEL9-003" }, + { 0x6e60, 0x83, "LEVEL9-003" }, + { 0x5b58, 0x50, "LEVEL9-003" }, + { 0x63b6, 0x2e, "LEVEL9-003" }, + { 0x6968, 0x32, "LEVEL9-003" }, + { 0x5b50, 0x66, "LEVEL9-003" }, + { 0x6970, 0xd6, "LEVEL9-003" }, + { 0x5ace, 0x11, "LEVEL9-003" }, + { 0x6e5c, 0xf6, "LEVEL9-003" }, + { 0x1929, 0x00, "LEVEL9-004-DEMO" }, + { 0x40e0, 0x02, "LEVEL9-004-DEMO" }, + { 0x3ebb, 0x00, "LEVEL9-004-en" }, + { 0x3e4f, 0x00, "LEVEL9-004-en" }, + { 0x3e8f, 0x00, "LEVEL9-004-en" }, + { 0x0fd8, 0x00, "LEVEL9-004-en" }, + { 0x14a3, 0x00, "LEVEL9-004-en" }, + { 0x110f, 0x00, "LEVEL9-004-fr" }, + { 0x4872, 0x00, "LEVEL9-004-de" }, + { 0x4846, 0x00, "LEVEL9-004-de" }, + { 0x11f5, 0x00, "LEVEL9-004-de" }, + { 0x11f5, 0x00, "LEVEL9-004-de" }, + { 0x76f4, 0x5e, "LEVEL9-005" }, + { 0x5b16, 0x3b, "LEVEL9-005" }, + { 0x6c8e, 0xb6, "LEVEL9-005" }, + { 0x6f4d, 0xcb, "LEVEL9-005" }, + { 0x6f6a, 0xa5, "LEVEL9-005" }, + { 0x5e31, 0x7c, "LEVEL9-005" }, + { 0x6f70, 0x40, "LEVEL9-005" }, + { 0x6f6e, 0x78, "LEVEL9-005" }, + { 0x5a8e, 0xf2, "LEVEL9-005" }, + { 0x76f4, 0x5a, "LEVEL9-005" }, + { 0x630e, 0x8d, "LEVEL9-006" }, + { 0x630e, 0xbe, "LEVEL9-006" }, + { 0x6f0c, 0x95, "LEVEL9-006" }, + { 0x593a, 0x80, "LEVEL9-006" }, + { 0x6bd2, 0x65, "LEVEL9-006" }, + { 0x6dc0, 0x63, "LEVEL9-006" }, + { 0x58a6, 0x24, "LEVEL9-006" }, + { 0x6de8, 0x4c, "LEVEL9-006" }, + { 0x58a3, 0x38, "LEVEL9-006" }, + { 0x63be, 0xd6, "LEVEL9-007" }, + { 0x378c, 0x8d, "LEVEL9-007" }, + { 0x63be, 0x0a, "LEVEL9-007" }, + { 0x34b3, 0x20, "LEVEL9-008" }, + { 0x34b3, 0xc7, "LEVEL9-008" }, + { 0x34b3, 0x53, "LEVEL9-008" }, + { 0xb1a9, 0x80, "LEVEL9-009-1" }, + { 0x908e, 0x0d, "LEVEL9-009-1" }, + { 0xad41, 0xa8, "LEVEL9-009-1" }, + { 0xb1aa, 0xad, "LEVEL9-009-1" }, + { 0x8aab, 0xc0, "LEVEL9-009-1" }, + { 0xb0ec, 0xc2, "LEVEL9-009-1" }, + { 0xb19e, 0x92, "LEVEL9-009-1" }, + { 0x5ff0, 0xf8, "LEVEL9-009-1" }, + { 0x52aa, 0xdf, "LEVEL9-009-1" }, + { 0xab9d, 0x31, "LEVEL9-009-2" }, + { 0x8f6f, 0x0a, "LEVEL9-009-2" }, + { 0xa735, 0xf7, "LEVEL9-009-2" }, + { 0xab8b, 0xbf, "LEVEL9-009-2" }, + { 0x8ac8, 0x9a, "LEVEL9-009-2" }, + { 0xaf82, 0x83, "LEVEL9-009-2" }, + { 0x6024, 0x01, "LEVEL9-009-2" }, + { 0x6ffa, 0xdb, "LEVEL9-009-2" }, + { 0xae28, 0x87, "LEVEL9-009-3" }, + { 0x9060, 0xbb, "LEVEL9-009-3" }, + { 0xa9c0, 0x9e, "LEVEL9-009-3" }, + { 0xae16, 0x81, "LEVEL9-009-3" }, + { 0x8a93, 0x4f, "LEVEL9-009-3" }, + { 0xb3e6, 0xab, "LEVEL9-009-3" }, + { 0x6036, 0x3d, "LEVEL9-009-3" }, + { 0x723a, 0x69, "LEVEL9-009-3" }, + { 0xd188, 0x13, "LEVEL9-010-1" }, + { 0x9089, 0xce, "LEVEL9-010-1" }, + { 0xb770, 0x03, "LEVEL9-010-1" }, + { 0xd19b, 0xad, "LEVEL9-010-1" }, + { 0x8ab7, 0x68, "LEVEL9-010-1" }, + { 0xd183, 0x83, "LEVEL9-010-1" }, + { 0x5a38, 0xf7, "LEVEL9-010-1" }, + { 0x76a0, 0x3a, "LEVEL9-010-1" }, + { 0xc594, 0x03, "LEVEL9-010-2" }, + { 0x908d, 0x80, "LEVEL9-010-2" }, + { 0xb741, 0xb6, "LEVEL9-010-2" }, + { 0xc5a5, 0xfe, "LEVEL9-010-2" }, + { 0x8b1e, 0x84, "LEVEL9-010-2" }, + { 0xc58f, 0x65, "LEVEL9-010-2" }, + { 0x531a, 0xed, "LEVEL9-010-2" }, + { 0x7674, 0x0b, "LEVEL9-010-2" }, + { 0xd79f, 0xb5, "LEVEL9-010-3" }, + { 0x909e, 0x9f, "LEVEL9-010-3" }, + { 0xb791, 0xa1, "LEVEL9-010-3" }, + { 0xd7ae, 0x9e, "LEVEL9-010-3" }, + { 0x8b1c, 0xa8, "LEVEL9-010-3" }, + { 0xd79a, 0x57, "LEVEL9-010-3" }, + { 0x57e4, 0x19, "LEVEL9-010-3" }, + { 0x765e, 0xba, "LEVEL9-010-3" }, + { 0xbb93, 0x36, "LEVEL9-011-1" }, + { 0x898a, 0x43, "LEVEL9-011-1" }, + { 0x8970, 0x6b, "LEVEL9-011-1" }, + { 0xbb6e, 0xa6, "LEVEL9-011-1" }, + { 0x86d0, 0xb7, "LEVEL9-011-1" }, + { 0xbb6e, 0xad, "LEVEL9-011-1" }, + { 0x46ec, 0x64, "LEVEL9-011-1" }, + { 0x74e0, 0x92, "LEVEL9-011-1" }, + { 0xc58e, 0x4a, "LEVEL9-011-2" }, + { 0x8b9f, 0x61, "LEVEL9-011-2" }, + { 0x8b90, 0x4e, "LEVEL9-011-2" }, + { 0xc58e, 0x43, "LEVEL9-011-2" }, + { 0x8885, 0x22, "LEVEL9-011-2" }, + { 0x6140, 0x18, "LEVEL9-011-2" }, + { 0x6dbc, 0x97, "LEVEL9-011-2" }, + { 0xcb9a, 0x0f, "LEVEL9-011-3" }, + { 0x8af9, 0x61, "LEVEL9-011-3" }, + { 0x8aea, 0x4e, "LEVEL9-011-3" }, + { 0xcb9a, 0x08, "LEVEL9-011-3" }, + { 0x87e5, 0x0e, "LEVEL9-011-3" }, + { 0x640e, 0xc1, "LEVEL9-011-3" }, + { 0x7402, 0x07, "LEVEL9-011-3" }, + { 0xbba4, 0x94, "LEVEL9-012-1" }, + { 0xc0cf, 0x4e, "LEVEL9-012-1" }, + { 0x8afc, 0x07, "LEVEL9-012-1" }, + { 0x8feb, 0xba, "LEVEL9-012-1" }, + { 0xb4c9, 0x94, "LEVEL9-012-1" }, + { 0xc0bd, 0x57, "LEVEL9-012-1" }, + { 0x8ade, 0xf2, "LEVEL9-012-1" }, + { 0x4fd2, 0x9d, "LEVEL9-012-1" }, + { 0x5c7a, 0x44, "LEVEL9-012-1" }, + { 0x768c, 0xe8, "LEVEL9-012-1" }, + { 0xd0c0, 0x56, "LEVEL9-012-2" }, + { 0xd5e9, 0x6a, "LEVEL9-012-2" }, + { 0x8aec, 0x13, "LEVEL9-012-2" }, + { 0x8f6b, 0xfa, "LEVEL9-012-2" }, + { 0xb729, 0x51, "LEVEL9-012-2" }, + { 0xd5d7, 0x99, "LEVEL9-012-2" }, + { 0x8b0e, 0xfb, "LEVEL9-012-2" }, + { 0x4dac, 0xa8, "LEVEL9-012-2" }, + { 0x53a2, 0x1e, "LEVEL9-012-2" }, + { 0x76b0, 0x1d, "LEVEL9-012-2" }, + { 0xb6ac, 0xc6, "LEVEL9-012-3" }, + { 0xbb8f, 0x1a, "LEVEL9-012-3" }, + { 0x8aba, 0x0d, "LEVEL9-012-3" }, + { 0x8f71, 0x2f, "LEVEL9-012-3" }, + { 0xb702, 0xe4, "LEVEL9-012-3" }, + { 0xbb7d, 0x17, "LEVEL9-012-3" }, + { 0x8ab3, 0xc1, "LEVEL9-012-3" }, + { 0x4f96, 0x22, "LEVEL9-012-3" }, + { 0x5914, 0x22, "LEVEL9-012-3" }, + { 0x765e, 0x4f, "LEVEL9-012-3" }, + { 0x5eb9, 0x30, "LEVEL9-013" }, + { 0x5eb9, 0x5d, "LEVEL9-013" }, + { 0x5eb9, 0x6e, "LEVEL9-013" }, + { 0xb257, 0xf8, "LEVEL9-013" }, + { 0xb576, 0x2a, "LEVEL9-013" }, + { 0x8d78, 0x3a, "LEVEL9-013" }, + { 0x9070, 0x43, "LEVEL9-013" }, + { 0xb38c, 0x37, "LEVEL9-013" }, + { 0xb563, 0x6a, "LEVEL9-013" }, + { 0xb57c, 0x44, "LEVEL9-013" }, + { 0xb260, 0xe5, "LEVEL9-013" }, + { 0x8950, 0xa1, "LEVEL9-013" }, + { 0xb579, 0x89, "LEVEL9-013" }, + { 0x579e, 0x97, "LEVEL9-013" }, + { 0x69fe, 0x56, "LEVEL9-013" }, + { 0x6f1e, 0xda, "LEVEL9-013" }, + { 0x5671, 0xbc, "LEVEL9-014" }, + { 0x6fc6, 0x14, "LEVEL9-014" }, + { 0x5aa4, 0xc1, "LEVEL9-014" }, + { 0x7410, 0x5e, "LEVEL9-014" }, + { 0x5aa4, 0xc1, "LEVEL9-014" }, + { 0x5aa4, 0xc1, "LEVEL9-014" }, + { 0xb797, 0x1f, "LEVEL9-014" }, + { 0xbaca, 0x3a, "LEVEL9-014" }, + { 0x8c46, 0xf0, "LEVEL9-014" }, + { 0x8f51, 0xb2, "LEVEL9-014" }, + { 0xb451, 0xa8, "LEVEL9-014" }, + { 0xbab2, 0x87, "LEVEL9-014" }, + { 0xbac7, 0x7f, "LEVEL9-014" }, + { 0xb7a0, 0x7e, "LEVEL9-014" }, + { 0x8a60, 0x2a, "LEVEL9-014" }, + { 0xbac4, 0x80, "LEVEL9-014" }, + { 0x579a, 0x2a, "LEVEL9-014" }, + { 0x5a50, 0xa9, "LEVEL9-014" }, + { 0x6108, 0xdd, "LEVEL9-014" }, + { 0x506c, 0xf0, "LEVEL9-015" }, + { 0x505d, 0x32, "LEVEL9-015" }, + { 0xa398, 0x82, "LEVEL9-015" }, + { 0xa692, 0xd1, "LEVEL9-015" }, + { 0x8d56, 0xd3, "LEVEL9-015" }, + { 0x903f, 0x6b, "LEVEL9-015" }, + { 0xa4e2, 0xa6, "LEVEL9-015" }, + { 0xa67c, 0xb8, "LEVEL9-015" }, + { 0xa69e, 0x6c, "LEVEL9-015" }, + { 0xa3a4, 0xdf, "LEVEL9-015" }, + { 0x8813, 0x11, "LEVEL9-015" }, + { 0xa698, 0x41, "LEVEL9-015" }, + { 0x5500, 0x50, "LEVEL9-015" }, + { 0x6888, 0x8d, "LEVEL9-015" }, + { 0x6da0, 0xb8, "LEVEL9-015" }, + { 0x6064, 0xbd, "LEVEL9-016" }, + { 0x6064, 0x01, "LEVEL9-016" }, + { 0x6047, 0x6c, "LEVEL9-016" }, + { 0x6064, 0xda, "LEVEL9-016" }, + { 0x6064, 0x95, "LEVEL9-016" }, + { 0x60c4, 0x28, "LEVEL9-016" }, + { 0x5cb7, 0xfe, "LEVEL9-016" }, + { 0x5ca1, 0x33, "LEVEL9-016" }, + { 0x5cb7, 0x64, "LEVEL9-016" }, + { 0x7d16, 0xe6, "LEVEL9-016" }, + { 0x639c, 0x8b, "LEVEL9-016" }, + { 0x60f7, 0x68, "LEVEL9-016" }, + { 0x772f, 0xca, "LEVEL9-016" }, + { 0x7cff, 0xf8, "LEVEL9-016" }, + { 0x7cf8, 0x24, "LEVEL9-016" }, + { 0x7d14, 0xe8, "LEVEL9-016" }, + { 0x7c55, 0x18, "LEVEL9-016" }, + { 0x5f43, 0xca, "LEVEL9-016" }, + { 0xc132, 0x14, "LEVEL9-017-1" }, + { 0xbeab, 0x2d, "LEVEL9-017-1" }, + { 0x9058, 0xcf, "LEVEL9-017-1" }, + { 0xbe94, 0xcc, "LEVEL9-017-1" }, + { 0x8a21, 0xf4, "LEVEL9-017-1" }, + { 0x55ce, 0xa1, "LEVEL9-017-1" }, + { 0x5cbc, 0xa5, "LEVEL9-017-1" }, + { 0x762e, 0x82, "LEVEL9-017-1" }, + { 0x99bd, 0x65, "LEVEL9-017-2" }, + { 0x8f43, 0xc9, "LEVEL9-017-2" }, + { 0x8a12, 0xe3, "LEVEL9-017-2" }, + { 0x54a6, 0xa9, "LEVEL9-017-2" }, + { 0x5932, 0x4e, "LEVEL9-017-2" }, + { 0x5bd6, 0x35, "LEVEL9-017-2" }, + { 0xbcb6, 0x7a, "LEVEL9-017-3 (Amiga/PC/ST)" }, + { 0x90ac, 0x68, "LEVEL9-017-3" }, + { 0x8a16, 0xcc, "LEVEL9-017-3" }, + { 0x51bc, 0xe3, "LEVEL9-017-3" }, + { 0x5860, 0x95, "LEVEL9-017-3" }, + { 0x6fa8, 0xa4, "LEVEL9-017-3" }, + { 0x5fab, 0x5c, "LEVEL9-018" }, + { 0x5fab, 0x2f, "LEVEL9-018" }, + { 0x7b31, 0x6e, "LEVEL9-018" }, + { 0x67a3, 0x9d, "LEVEL9-018" }, + { 0x6bf8, 0x3f, "LEVEL9-018" }, + { 0x7363, 0x65, "LEVEL9-018" }, + { 0x7b2f, 0x70, "LEVEL9-018" }, + { 0x7b2f, 0x70, "LEVEL9-018" }, + { 0x6541, 0x02, "LEVEL9-018" }, + { 0x5834, 0x42, "LEVEL9-019-1" }, + { 0x765d, 0xcd, "LEVEL9-019-1" }, + { 0x6ce5, 0x58, "LEVEL9-019-1" }, + { 0x56dd, 0x51, "LEVEL9-019-2" }, + { 0x6e58, 0x07, "LEVEL9-019-2" }, + { 0x68da, 0xc1, "LEVEL9-019-2" }, + { 0x5801, 0x53, "LEVEL9-019-3" }, + { 0x7e98, 0x6a, "LEVEL9-019-3" }, + { 0x6c67, 0x9a, "LEVEL9-019-3" }, + { 0x54a4, 0x01, "LEVEL9-019-4" }, + { 0x81e2, 0xd5, "LEVEL9-019-4" }, + { 0x6d91, 0xb9, "LEVEL9-019-4" }, + { 0x5828, 0xbd, "LEVEL9-020" }, + { 0x6d84, 0xf9, "LEVEL9-020" }, + { 0x6d84, 0xc8, "LEVEL9-020" }, + { 0x6030, 0x47, "LEVEL9-020" }, + { 0x772b, 0xcd, "LEVEL9-020" }, + { 0x546c, 0xb7, "LEVEL9-020" }, + { 0x7cd9, 0x0c, "LEVEL9-020" }, + { 0x60dd, 0xf2, "LEVEL9-020" }, + { 0x6161, 0xf3, "LEVEL9-020" }, + { 0x788d, 0x72, "LEVEL9-020" }, + { 0x7cd7, 0x0e, "LEVEL9-020" }, + { 0x5ebb, 0xf1, "LEVEL9-020" }, + + { 0, 0, NULL } +}; + + + +static int32 read_l9_int(unsigned char *sf) +{ + return ((int32) sf[1]) << 8 | sf[0]; + +} +static int v2_recognition (unsigned char *sf, int32 extent, int32 *l, unsigned char *c) +{ + int32 i, j; + for (i=0;i0x4000) && (*l<=0xdb00))) + if ((*l!=0) && (sf[i+0x0d] == 0)) + for (j=i;j0x0fd0) && (end <= (extent - 2)) && + (((read_l9_int(sf+i+2) + read_l9_int(sf+i+4))==read_l9_int(sf+i+6)) + && (read_l9_int(sf+i+2) != 0) && (read_l9_int(sf+i+4)) != 0) && + (((read_l9_int(sf+i+6) + read_l9_int(sf+i+8)) == read_l9_int(sf+i+10)) + && ((sf[i + 18] == 0x2a) || (sf[i + 18] == 0x2c)) + && (sf[i + 19] == 0) && (sf[i + 20] == 0) && (sf[i + 21] == 0))) + ll = 2; + } + if (ll>1) + { + *c=0; + if (phase==3) ll=1; + else + { char checksum=0; + *c = sf[end]; + for (j=i;j<=end;j++) + checksum += sf[j]; + if (!checksum) ll=1; + else ll=0; + } + } else ll=0; + } + + if (ll) return *l < 0x8500 ? 3:4; + return 0; +} +static char *get_l9_ifid(int32 length, unsigned char chk) +{ + int i; + for(i=0;l9_registry[i].length;i++) + if (length==l9_registry[i].length && chk==l9_registry[i].chk) return l9_registry[i].ifid; + return NULL; +} +static int get_l9_version(unsigned char *sf, int32 extent, char **ifid) +{ + int i; + int32 l; + unsigned char c; + if (v2_recognition(sf,extent, &l, &c)) { *ifid=get_l9_ifid(l,c); return 2; } + l=0; c=0; + i=v3_recognition_phase(1,sf,extent, &l, &c); + if (i) { *ifid=get_l9_ifid(l,c); return i; } + if (v1_recognition(sf,extent, ifid)) return 1; + l=0; c=0; + i=v3_recognition_phase(2,sf,extent, &l, &c); + if (i) { *ifid=get_l9_ifid(l,c); return i; } + i=v3_recognition_phase(3,sf,extent, &l, &c); + *ifid=NULL; + return i; +} + +static int32 claim_story_file(void *story, int32 extent) +{ + char *ifid=NULL; + if (get_l9_version((unsigned char *) story,extent, &ifid)) + if (ifid) return VALID_STORY_FILE_RV; + else return NO_REPLY_RV; + return INVALID_STORY_FILE_RV; +} + + + +static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) +{ + char *ifid=NULL; + int i=get_l9_version((unsigned char *)story_file, extent, &ifid); + if (!i) return INVALID_STORY_FILE_RV; + if (ifid) + { + ASSERT_OUTPUT_SIZE((signed) strlen(ifid)+1); + strcpy(output,ifid); + return 1; + } + ASSERT_OUTPUT_SIZE(10); + sprintf(output,"LEVEL9-%d-",i); + return INCOMPLETE_REPLY_RV; +} diff --git a/babel/magscrolls.c b/babel/magscrolls.c new file mode 100644 index 0000000..c21c922 --- /dev/null +++ b/babel/magscrolls.c @@ -0,0 +1,124 @@ +/* magscrolls.c Treaty of Babel module for Z-code files + * 2006 By L. Ross Raszewski + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file + * may render it noncompliant with the Treaty of Babel + */ + +#define FORMAT magscrolls +#define HOME_PAGE "http://www.if-legends.org/~msmemorial/memorial.htm" +#define FORMAT_EXT ".mag" +#define NO_COVER +#define NO_METADATA + +#include "treaty_builder.h" +#include +#include + +struct maginfo +{ + int gv; + char header[21]; + char *title; + int bafn; + int year; + char *ifid; + char *author; + char *meta; +}; + + +static struct maginfo manifest[] = { + { 0, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", + "The Pawn", + 0, + 1985, + "MAGNETIC-1", + "Rob Steggles", + }, + { 1, "\000\004\000\001\007\370\000\000\340\000\000\000\041\064\000\000\040\160\000\000", + "Guild of Thieves", + 0, + 1987, + "MAGNETIC-2", + "Rob Steggles", + }, + { 2, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", + "Jinxter", + 0, + 1987, + "MAGNETIC-3", + "Georgina Sinclair and Michael Bywater", + }, + { 4, "\000\004\000\001\045\140\000\001\000\000\000\000\161\017\000\000\035\210\000\001", + "Corruption", + 0, + 1988, + "MAGNETIC-4", + "Rob Steggles and Hugh Steers", + }, + { 4, "\000\004\000\001\044\304\000\001\000\000\000\000\134\137\000\000\040\230\000\001", + "Fish!", + 0, + 1988, + "MAGNETIC-5", + "John Molloy, Pete Kemp, Phil South, Rob Steggles", + }, + { 4, "\000\003\000\000\377\000\000\000\340\000\000\000\221\000\000\000\036\000\000\001", + "Corruption", + 0, + 1988, + "MAGNETIC-4", + "Rob Steggles and Hugh Steers", + }, + { 4, "\000\003\000\001\000\000\000\000\340\000\000\000\175\000\000\000\037\000\000\001", + "Fish!", + 0, + 1988, + "MAGNETIC-5", + "John Molloy, Pete Kemp, Phil South, Rob Steggles", + }, + { 4, "\000\003\000\000\335\000\000\000\140\000\000\000\064\000\000\000\023\000\000\000", + "Myth", + 0, + 1989, + "MAGNETIC-6", + "Paul Findley", + }, + { 4, "\000\004\000\001\122\074\000\001\000\000\000\000\114\146\000\000\057\240\000\001", + "Wonderland", + 0, + 1990, + "MAGNETIC-7", + "David Bishop", + }, + { 0, "0", NULL, 0, 0, NULL, NULL } + }; + +static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) +{ + int i; + unsigned char *sf=(unsigned char *)story_file; + if (extent < 42) return INVALID_STORY_FILE_RV; + + for(i=0;manifest[i].title;i++) + if ((sf[13]<3 && manifest[i].gv==sf[13]) || memcmp(manifest[i].header,sf+12,20)==0) + { + ASSERT_OUTPUT_SIZE(((int32) strlen(manifest[i].ifid)+1)); + strcpy(output,manifest[i].ifid); + return 1; + } + strcpy(output,"MAGNETIC-"); + return INCOMPLETE_REPLY_RV; +} + +static int32 claim_story_file(void *story_file, int32 extent) +{ + if (extent<42 || + memcmp(story_file,"MaSc",4) + ) return INVALID_STORY_FILE_RV; + return VALID_STORY_FILE_RV; +} + diff --git a/babel/md5.c b/babel/md5.c new file mode 100644 index 0000000..c35d96c --- /dev/null +++ b/babel/md5.c @@ -0,0 +1,381 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5.h" +#include + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} diff --git a/babel/md5.h b/babel/md5.h new file mode 100644 index 0000000..698c995 --- /dev/null +++ b/babel/md5.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/babel/misc.c b/babel/misc.c new file mode 100644 index 0000000..982927f --- /dev/null +++ b/babel/misc.c @@ -0,0 +1,19 @@ +/* misc.h : miscellany for babel + * This file is public domain + * 2006 by L. Ross Raszewski + */ + +#include +#include + +void *my_malloc(int size, char *rs) +{ + void *buf=calloc(size,1); + if (size && !buf) + { + fprintf(stderr,"Error: Memory exceeded (%d for %s)!\n",size,rs); + exit(2); + } + return buf; +} + diff --git a/babel/modules.h b/babel/modules.h new file mode 100644 index 0000000..cc799ee --- /dev/null +++ b/babel/modules.h @@ -0,0 +1,70 @@ +/* modules.h Declaration of treaty modules for the babel program + * (c) 2006 By L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends upon treaty.h and all the references treaty modules + * + * Persons wishing to add support for a new module to babel need only + * add a line in the form below. New modules should be positioned according + * to their popularity. If this file is being used in tandem with register.c + * (as it is in babel), then being dishonest about the popularity of an added + * system will make the program non-compliant with the treaty of Babel + * + * REGISTER_NAME is used as a placeholder for formats which are specified + * as existing by the treaty but for which no handler yet exists. + * remove the REGISTER_NAME for any format which has a registered treaty. + */ + + +#include "treaty.h" +#undef REGISTER_TREATY +#undef REGISTER_CONTAINER +#undef REGISTER_NAME +#ifdef TREATY_REGISTER +#ifdef CONTAINER_REGISTER +#ifdef FORMAT_REGISTER +#define REGISTER_TREATY(x) #x, +#define REGISTER_NAME(x) #x, +#define REGISTER_CONTAINER(x) +#else +#define REGISTER_TREATY(x) +#define REGISTER_CONTAINER(x) x##_treaty, +#define REGISTER_NAME(x) +#endif +#else +#define REGISTER_TREATY(x) x##_treaty, +#define REGISTER_CONTAINER(x) +#define REGISTER_NAME(x) +#endif +#else +#define REGISTER_TREATY(x) int32 x##_treaty(int32, void *, int32, void *, int32); +#define REGISTER_CONTAINER(x) int32 x##_treaty(int32, void *, int32, void *, int32); +#define REGISTER_NAME(x) +#endif + + +REGISTER_CONTAINER(blorb) +REGISTER_TREATY(zcode) +REGISTER_TREATY(glulx) +REGISTER_TREATY(tads2) +REGISTER_TREATY(tads3) +REGISTER_TREATY(hugo) +REGISTER_TREATY(alan) +REGISTER_TREATY(adrift) +REGISTER_TREATY(level9) +REGISTER_TREATY(agt) +REGISTER_TREATY(magscrolls) +REGISTER_TREATY(advsys) +REGISTER_TREATY(executable) + + + + diff --git a/babel/modules.h.gch b/babel/modules.h.gch new file mode 100644 index 0000000..99db82c Binary files /dev/null and b/babel/modules.h.gch differ diff --git a/babel/register.c b/babel/register.c new file mode 100644 index 0000000..22bbed5 --- /dev/null +++ b/babel/register.c @@ -0,0 +1,36 @@ +/* register.c Register modules for the babel handler api + * + * 2006 by L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends on modules.h + * + * The purpose of this file is to create the treaty_registry array. + * This array is a null-terminated list of the known treaty modules. + */ + +#include +#include "modules.h" + + +TREATY treaty_registry[] = { + #define TREATY_REGISTER + #include "modules.h" + NULL + }; + +TREATY container_registry[] = { + #define CONTAINER_REGISTER + #include "modules.h" + NULL + +}; + diff --git a/babel/register_ifiction.c b/babel/register_ifiction.c new file mode 100644 index 0000000..55fe9e2 --- /dev/null +++ b/babel/register_ifiction.c @@ -0,0 +1,29 @@ +/* register_ifiction.c Register modules for babel's ifiction API + * + * 2006 by L. Ross Raszewski + * + * This code is freely usable for all purposes. + * + * This work is licensed under the Creative Commons Attribution2.5 License. + * To view a copy of this license, visit + * http://creativecommons.org/licenses/by/2.5/ or send a letter to + * Creative Commons, + * 543 Howard Street, 5th Floor, + * San Francisco, California, 94105, USA. + * + * This file depends on modules.h + * + * This version of register.c is stripped down to include only the + * needed functionality for the ifiction api + */ + +#include +#include "treaty.h" + +char *format_registry[] = { + #define TREATY_REGISTER + #define CONTAINER_REGISTER + #define FORMAT_REGISTER + #include "modules.h" + NULL +}; diff --git a/babel/tads.c b/babel/tads.c new file mode 100644 index 0000000..bde1e5a --- /dev/null +++ b/babel/tads.c @@ -0,0 +1,1827 @@ +/* + * tads.c - Treaty of Babel common functions for tads2 and tads3 modules + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file may + * render it noncompliant with the Treaty of Babel + * + * Modified + *. 04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions + *. 04/08/2006 MJRoberts - initial implementation + */ + + +#include "treaty.h" +#include +#include +#include +#include +#include "tads.h" +#include "md5.h" + +#define ASSERT_OUTPUT_SIZE(x) \ + do { if (output_extent < (x)) return INVALID_USAGE_RV; } while (0) + +#define T2_SIGNATURE "TADS2 bin\012\015\032" +#define T3_SIGNATURE "T3-image\015\012\032" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/* ------------------------------------------------------------------------ */ +/* + * private structures + */ + +/* + * resource information structure - this encapsulates the location and size + * of a binary resource object embedded in a story file + */ +typedef struct resinfo resinfo; +struct resinfo +{ + /* pointer and length of the data in the story file buffer */ + const char *ptr; + int32 len; + + /* tads major version (currently, 2 or 3) */ + int tads_version; +}; + +/* + * Name/value pair list entry + */ +typedef struct valinfo valinfo; +struct valinfo +{ + const char *name; + size_t name_len; + + /* value string */ + char *val; + size_t val_len; + + /* next entry in the list */ + valinfo *nxt; +}; + + +/* ------------------------------------------------------------------------ */ +/* + * forward declarations + */ +static valinfo *parse_game_info(const void *story_file, int32 story_len, + int *version); +static int find_resource(const void *story_file, int32 story_len, + const char *resname, resinfo *info); +static int find_cover_art(const void *story_file, int32 story_len, + resinfo *resp, int32 *image_format, + int32 *width, int32 *height); +static int t2_find_res(const void *story_file, int32 story_len, + const char *resname, resinfo *info); +static int t3_find_res(const void *story_file, int32 story_len, + const char *resname, resinfo *info); +static valinfo *find_by_key(valinfo *list_head, const char *key); +static void delete_valinfo_list(valinfo *head); +static int32 generate_md5_ifid(void *story_file, int32 extent, + char *output, int32 output_extent); +static int32 synth_ifiction(valinfo *vals, int tads_version, + char *buf, int32 bufsize, + void *story_file, int32 extent); +static int get_png_dim(const void *img, int32 extent, + int32 *xout, int32 *yout); +static int get_jpeg_dim(const void *img, int32 extent, + int32 *xout, int32 *yout); + + + +/* ------------------------------------------------------------------------ */ +/* + * Get the IFID for a given story file. + */ +int32 tads_get_story_file_IFID(void *story_file, int32 extent, + char *output, int32 output_extent) +{ + valinfo *vals; + + /* if we have GameInfo, try looking for an IFID there */ + if ((vals = parse_game_info(story_file, extent, 0)) != 0) + { + valinfo *val; + int found = 0; + + /* find the "IFID" key */ + if ((val = find_by_key(vals, "IFID")) != 0) + { + char *p; + + /* copy the output as a null-terminated string */ + ASSERT_OUTPUT_SIZE((int32)val->val_len + 1); + memcpy(output, val->val, val->val_len); + output[val->val_len] = '\0'; + + /* + * count up the IFIDs in the buffer - there might be more than + * one, separated by commas + */ + for (found = 1, p = output ; *p != '\0' ; ++p) + { + /* if this is a comma, it delimits a new IFID */ + if (*p == ',') + ++found; + } + } + + /* delete the GameInfo list */ + delete_valinfo_list(vals); + + /* if we found an IFID, indicate how many results we found */ + if (found != 0) + return found; + } + + /* + * we didn't find an IFID in the GameInfo, so generate a default IFID + * using the MD5 method + */ + return generate_md5_ifid(story_file, extent, output, output_extent); +} + +/* + * Get the size of the ifiction metadata for the game + */ +int32 tads_get_story_file_metadata_extent(void *story_file, int32 extent) +{ + valinfo *vals; + int32 ret; + int ver; + + /* + * First, make sure we have a GameInfo record. If we don't, simply + * indicate that there's no metadata to fetch. + */ + if ((vals = parse_game_info(story_file, extent, &ver)) == 0) + return NO_REPLY_RV; + + /* + * Run the ifiction synthesizer with no output buffer, to calculate the + * size we need. + */ + ret = synth_ifiction(vals, ver, 0, 0, story_file, extent); + + /* delete the value list */ + delete_valinfo_list(vals); + + /* return the required size */ + return ret; +} + +/* + * Get the ifiction metadata for the game + */ +int32 tads_get_story_file_metadata(void *story_file, int32 extent, + char *buf, int32 bufsize) +{ + valinfo *vals; + int32 ret; + int ver; + + /* make sure we have metadata to fetch */ + if ((vals = parse_game_info(story_file, extent, &ver)) == 0) + return NO_REPLY_RV; + + /* synthesize the ifiction data into the output buffer */ + ret = synth_ifiction(vals, ver, buf, bufsize, story_file, extent); + + /* if that required more space than we had available, return an error */ + if (ret > bufsize) + ret = INVALID_USAGE_RV; + + /* delete the value list */ + delete_valinfo_list(vals); + + /* return the result */ + return ret; +} + +/* + * Get the size of the cover art + */ +int32 tads_get_story_file_cover_extent(void *story_file, int32 story_len) +{ + resinfo res; + + /* look for the cover art resource */ + if (find_cover_art(story_file, story_len, &res, 0, 0, 0)) + return res.len; + else + return NO_REPLY_RV; +} + +/* + * Get the format of the cover art + */ +int32 tads_get_story_file_cover_format(void *story_file, int32 story_len) +{ + int32 typ; + + /* look for CoverArt.jpg */ + if (find_cover_art(story_file, story_len, 0, &typ, 0, 0)) + return typ; + else + return NO_REPLY_RV; +} + +/* + * Get the cover art data + */ +int32 tads_get_story_file_cover(void *story_file, int32 story_len, + void *outbuf, int32 output_extent) +{ + resinfo res; + + /* look for CoverArt.jpg, then for CoverArt.png */ + if (find_cover_art(story_file, story_len, &res, 0, 0, 0)) + { + /* got it - copy the data to the buffer */ + ASSERT_OUTPUT_SIZE(res.len); + memcpy(outbuf, res.ptr, res.len); + + /* success */ + return res.len; + } + + /* otherwise, we didn't find it */ + return NO_REPLY_RV; +} + +/* ------------------------------------------------------------------------ */ +/* + * Generate a default IFID using the MD5 hash method + */ +static int32 generate_md5_ifid(void *story_file, int32 extent, + char *output, int32 output_extent) +{ + md5_state_t md5; + unsigned char md5_buf[16]; + char *p; + int i; + + /* calculate the MD5 hash of the story file */ + md5_init(&md5); + md5_append(&md5, story_file, extent); + md5_finish(&md5, md5_buf); + + /* make sure we have room to store the result */ + ASSERT_OUTPUT_SIZE(39); + + /* the prefix is "TADS2-" or "TADS3-", depending on the format */ + if (tads_match_sig(story_file, extent, T2_SIGNATURE)) + strcpy(output, "TADS2-"); + else + strcpy(output, "TADS3-"); + + /* the rest is the MD5 hash of the file, as hex digits */ + for (i = 0, p = output + strlen(output) ; i < 16 ; p += 2, ++i) + sprintf(p, "%02X", md5_buf[i]); + + /* indicate that we found one result */ + return 1; +} + +/* ------------------------------------------------------------------------ */ +/* + * Some UTF-8 utility functions and macros. We use our own rather than the + * ctype.h macros because we're parsing UTF-8 text. + */ + +/* is c a space? */ +#define u_isspace(c) ((unsigned char)(c) < 128 && isspace(c)) + +/* is c a horizontal space? */ +#define u_ishspace(c) (u_isspace(c) && (c) != '\n' && (c) != '\r') + +/* is-newline - matches \n, \r, and \u2028 */ +static int u_isnl(const char *p, int32 len) +{ + return (*p == '\n' + || *p == '\r' + || (len >= 3 + && *(unsigned char *)p == 0xe2 + && *(unsigned char *)(p+1) == 0x80 + && *(unsigned char *)(p+2) == 0xa8)); +} + +/* skip to the next utf-8 character */ +static void nextc(const char **p, int32 *len) +{ + /* skip the first byte */ + if (*len != 0) + ++*p, --*len; + + /* skip continuation bytes */ + while (*len != 0 && (**p & 0xC0) == 0x80) + ++*p, --*len; +} + +/* skip to the previous utf-8 character */ +static void prevc(const char **p, int32 *len) +{ + /* move back one byte */ + --*p, ++*len; + + /* keep skipping as long as we're looking at continuation characters */ + while ((**p & 0xC0) == 0x80) + --*p, ++*len; +} + +/* + * Skip a newline sequence. Skips all common conventions, including \n, + * \r, \n\r, \r\n, and \u2028. + */ +static void skip_newline(const char **p, int32 *rem) +{ + /* make sure we have something to skip */ + if (*rem == 0) + return; + + /* check what we have */ + switch (**(const unsigned char **)p) + { + case '\n': + /* skip \n or \n\r */ + nextc(p, rem); + if (**p == '\r') + nextc(p, rem); + break; + + case '\r': + /* skip \r or \r\n */ + nextc(p, rem); + if (**p == '\n') + nextc(p, rem); + break; + + case 0xe2: + /* \u2028 (unicode line separator) - just skip the one character */ + nextc(p, rem); + break; + } +} + +/* + * Skip to the next line + */ +static void skip_to_next_line(const char **p, int32 *rem) +{ + /* look for the next newline */ + for ( ; *rem != 0 ; nextc(p, rem)) + { + /* if this is a newline of some kind, we're at the end of the line */ + if (u_isnl(*p, *rem)) + { + /* skip the newline, and we're done */ + skip_newline(p, rem); + break; + } + } +} + + +/* ------------------------------------------------------------------------ */ +/* + * ifiction synthesizer output context + */ +typedef struct synthctx synthctx; +struct synthctx +{ + /* the current output pointer */ + char *buf; + + /* the number of bytes remaining in the output buffer */ + int32 buf_size; + + /* + * the total number of bytes needed for the output (this might be more + * than we've actually written, since we count up the bytes required + * even if we need more space than the buffer provides) + */ + int32 total_size; + + /* the head of the name/value pair list from the parsed GameInfo */ + valinfo *vals; +}; + +/* initialize a synthesizer context */ +static void init_synthctx(synthctx *ctx, char *buf, int32 bufsize, + valinfo *vals) +{ + /* set up at the beginning of the output buffer */ + ctx->buf = buf; + ctx->buf_size = bufsize; + + /* we haven't written anything to the output buffer yet */ + ctx->total_size = 0; + + /* remember the name/value pair list */ + ctx->vals = vals; +} + +/* + * Write out a chunk to a synthesized ifiction record, updating pointers + * and counters. We won't copy past the end of the buffer, but we'll + * continue counting the output length needed in any case. + */ +static void write_ifiction(synthctx *ctx, const char *src, size_t srclen) +{ + int32 copy_len; + + /* copy as much as we can, up to the remaining buffer size */ + copy_len = srclen; + if (copy_len > ctx->buf_size) + copy_len = ctx->buf_size; + + /* do the copying, if any */ + if (copy_len != 0) + { + /* copy the bytes */ + memcpy(ctx->buf, src, (size_t)copy_len); + + /* adjust the buffer pointer and output buffer size remaining */ + ctx->buf += copy_len; + ctx->buf_size -= copy_len; + } + + /* count this source data in the total size */ + ctx->total_size += srclen; +} + +/* write a null-terminated chunk to the synthesized ifiction record */ +static void write_ifiction_z(synthctx *ctx, const char *src) +{ + write_ifiction(ctx, src, strlen(src)); +} + +/* + * Write a PCDATA string to the synthesized ifiction record. In + * particular, we rewrite '<', '>', and '&' as '<', '>', and '&', + * respectively; we trim off leading and trailing spaces; and we compress + * each run of whitespace down to a single \u0020 (' ') character. + */ +static void write_ifiction_pcdata(synthctx *ctx, const char *p, size_t len) +{ + /* first, skip any leading whitespace */ + for ( ; len != 0 && u_ishspace(*p) ; ++p, --len) ; + + /* keep going until we run out of string */ + for (;;) + { + const char *start; + + /* scan to the next whitespace or markup-significant character */ + for (start = p ; + len != 0 && !u_ishspace(*p) + && *p != '<' && *p != '>' && *p != '&' ; ++p, --len) ; + + /* write the part up to here */ + if (p != start) + write_ifiction(ctx, start, p - start); + + /* if we've reached the end of the string, we can stop */ + if (len == 0) + break; + + /* check what stopped us */ + switch (*p) + { + case '<': + write_ifiction_z(ctx, "<"); + ++p, --len; + break; + + case '>': + write_ifiction_z(ctx, ">"); + ++p, --len; + break; + + case '&': + write_ifiction_z(ctx, "&"); + ++p, --len; + break; + + default: + /* + * The only other thing that could have stopped us is + * whitespace. Skip all consecutive whitespace. + */ + for ( ; len != 0 && u_ishspace(*p) ; ++p, --len); + + /* + * if that's not the end of the string, replace the run of + * whitespace with a single space character in the output; if + * we've reached the end of the string, we don't even want to + * do that, since we want to trim off trailing spaces + */ + if (len != 0) + write_ifiction_z(ctx, " "); + break; + } + } +} + +/* + * Translate a GameInfo keyed value to the corresponding ifiction tagged + * value. We find the GameInfo value keyed by 'gameinfo_key', and write + * out the same string under the ifiction XML tag 'ifiction_tag'. We write + * a complete XML container sequence - value. + * + * If the given GameInfo key doesn't exist, we use the default value string + * 'dflt', if given. If the GameInfo key doesn't exist and 'dflt' is null, + * we don't write anything - we don't even write the open/close tags. + * + * If 'html' is true, we assume the value is in html format, and we write + * it untranslated. Otherwise, we write it as PCDATA, translating markup + * characters into '&' entities and compressing whitespace. + */ +static void write_ifiction_xlat_base(synthctx *ctx, int indent, + const char *gameinfo_key, + const char *ifiction_tag, + const char *dflt, int html) +{ + valinfo *val; + const char *valstr; + size_t vallen; + + /* look up the GameInfo key */ + if ((val = find_by_key(ctx->vals, gameinfo_key)) != 0) + { + /* we found the GameInfo value - use it */ + valstr = val->val; + vallen = val->val_len; + } + else if (dflt != 0) + { + /* the GameInfo value doesn't exist, but we have a default - use it */ + valstr = dflt; + vallen = strlen(dflt); + } + else + { + /* there's no GameInfo value and no default, so write nothing */ + return; + } + + /* write the indentation */ + while (indent != 0) + { + static const char spaces[] = " "; + size_t cur; + + /* figure how much we can write on this round */ + cur = indent; + if (cur > sizeof(spaces) - 1) + cur = sizeof(spaces) - 1; + + /* write it */ + write_ifiction(ctx, spaces, cur); + + /* deduct it from the amount remaining */ + indent -= cur; + } + + /* write the open tag */ + write_ifiction_z(ctx, "<"); + write_ifiction_z(ctx, ifiction_tag); + write_ifiction_z(ctx, ">"); + + /* write the value, applying pcdata translations */ + if (html) + write_ifiction(ctx, valstr, vallen); + else + write_ifiction_pcdata(ctx, valstr, vallen); + + /* write the close tag */ + write_ifiction_z(ctx, "\n"); +} + +#define write_ifiction_xlat(ctx, indent, gikey, iftag, dflt) \ + write_ifiction_xlat_base(ctx, indent, gikey, iftag, dflt, FALSE) + +#define write_ifiction_xlat_html(ctx, indent, gikey, iftag, dflt) \ + write_ifiction_xlat_base(ctx, indent, gikey, iftag, dflt, TRUE) + + +/* + * Retrieve the next author name from the GameInfo "Author" format. The + * format is as follows: + * + * name ... ; ... + * + * That is, each author is listed with a name followed by one or more email + * addresses in angle brackets, and multiple authors are separated by + * semicolons. + */ +static int scan_author_name(const char **p, size_t *len, + const char **start, const char **end) +{ + /* keep going until we find a non-empty author name */ + for (;;) + { + /* skip leading spaces */ + for ( ; *len != 0 && u_ishspace(**p) ; ++*p, --*len) ; + + /* if we ran out of string, there's definitely no author name */ + if (*len == 0) + return FALSE; + + /* + * Find the end of this author name. The author name ends at the + * next semicolon or angle bracket. + */ + for (*start = *p ; *len != 0 && **p != ';' && **p != '<' ; + ++*p, --*len) ; + + /* trim off any trailing spaces */ + for (*end = *p ; *end > *start && u_ishspace(*(*end - 1)) ; --*end) ; + + /* now skip any email addresses */ + while (*len != 0 && **p == '<') + { + /* skip to the closing bracket */ + for (++*p, --*len ; *len != 0 && **p != '>' ; ++*p, --*len) ; + + /* skip the bracket */ + if (*len != 0) + ++*p, --*len; + + /* skip whitespace */ + for ( ; *len != 0 && u_ishspace(**p) ; ++*p, --*len) ; + + /* + * if we're not at a semicolon, another angle bracket, or the + * end of the string, it's a syntax error + */ + if (*len != 0 && **p != '<' && **p != ';') + { + *len = 0; + return FALSE; + } + } + + /* if we're at a semicolon, skip it */ + if (*len != 0 && **p == ';') + ++*p, --*len; + + /* + * if we found a non-empty name, return it; otherwise, continue on + * to the next semicolon section + */ + if (*end != *start) + return TRUE; + } +} + + +/* + * Synthesize an ifiction record for the given GameInfo name/value pair + * list. Returns the number of bytes required for the result, including + * null termination. We'll copy as much as we can to the output buffer, up + * to bufsize; if the buffer size is insufficient to hold the result, we'll + * still indicate the length needed for the full result, but we're careful + * not to actually copy anything past the end of the buffer. + */ +static int32 synth_ifiction(valinfo *vals, int tads_version, + char *buf, int32 bufsize, + void *story_file, int32 extent) +{ + char default_ifid[TREATY_MINIMUM_EXTENT]; + valinfo *ifid = find_by_key(vals, "IFID"); + const char *ifid_val; + size_t ifid_len; + valinfo *author = find_by_key(vals, "AuthorEmail"); + valinfo *url = find_by_key(vals, "Url"); + synthctx ctx; + const char *p; + size_t rem; + int32 art_fmt; + int32 art_wid, art_ht; + + /* initialize the output content */ + init_synthctx(&ctx, buf, bufsize, vals); + + /* make sure the tads version is one we know how to handle */ + if (tads_version != 2 && tads_version != 3) + return NO_REPLY_RV; + + /* + * The IFID is mandatory. If there's not an IFID specifically listed + * in the GameInfo, we need to generate the default IFID based on the + * MD5 hash of the game file. + */ + if (ifid != 0) + { + /* use the explicit IFID(s) listed in the GameInfo */ + ifid_val = ifid->val; + ifid_len = ifid->val_len; + } + else + { + /* generate the default IFID */ + generate_md5_ifid(story_file, extent, + default_ifid, TREATY_MINIMUM_EXTENT); + + /* use this as the IFID */ + ifid_val = default_ifid; + ifid_len = strlen(default_ifid); + } + + /* write the header, and start the section */ + write_ifiction_z( + &ctx, + "\n" + "\n" + " \n" + " \n" + " \n" + " Babel\n" + " " TREATY_VERSION "\n" + " 2006-04-14\n" + " \n" + " \n"); + + /* write each IFID (there might be several) */ + for (p = ifid_val, rem = ifid_len ; rem != 0 ; ) + { + const char *start; + const char *end; + + /* skip leading spaces */ + for ( ; rem != 0 && u_ishspace(*p) ; ++p, --rem) ; + + /* find the end of this IFID */ + for (start = p ; rem != 0 && *p != ',' ; ++p, --rem) ; + + /* remove trailing spaces */ + for (end = p ; end > start && u_ishspace(*(end-1)) ; --end) ; + + /* if we found one, write it out */ + if (end != start) + { + write_ifiction_z(&ctx, " "); + write_ifiction(&ctx, start, end - start); + write_ifiction_z(&ctx, "\n"); + } + + /* skip the comma */ + if (rem != 0 && *p == ',') + ++p, --rem; + } + + /* add the format information */ + write_ifiction_z(&ctx, + tads_version == 2 + ? " tads2\n" + : " tads3\n"); + + /* close the section and start the */ + write_ifiction_z(&ctx, + " \n" + " \n"); + + /* write the various bibliographic data */ + write_ifiction_xlat(&ctx, 6, "Name", "title", "An Interactive Fiction"); + write_ifiction_xlat(&ctx, 6, "Headline", "headline", 0); + write_ifiction_xlat(&ctx, 6, "Desc", "description", 0); + write_ifiction_xlat(&ctx, 6, "Genre", "genre", 0); + write_ifiction_xlat(&ctx, 6, "Forgiveness", "forgiveness", 0); + write_ifiction_xlat(&ctx, 6, "Series", "series", 0); + write_ifiction_xlat(&ctx, 6, "SeriesNumber", "seriesnumber", 0); + write_ifiction_xlat(&ctx, 6, "Language", "language", 0); + write_ifiction_xlat(&ctx, 6, "FirstPublished", "firstpublished", 0); + + /* if there's an author, write the list of author names */ + if (author != 0) + { + int cnt; + int i; + const char *start; + const char *end; + + /* start the tag */ + write_ifiction_z(&ctx, " "); + + /* + * first, count up the number of authors - authors are separated by + * semicolons, so there's one more author than there are semicolons + */ + for (p = author->val, rem = author->val_len, cnt = 1 ; + scan_author_name(&p, &rem, &start, &end) ; ) ; + + /* + * Now generate the list of authors. If there are multiple + * authors, use commas to separate them. + */ + for (p = author->val, rem = author->val_len, i = 0 ; ; ++i) + { + /* scan this author's name */ + if (!scan_author_name(&p, &rem, &start, &end)) + break; + + /* write out this author name */ + write_ifiction_pcdata(&ctx, start, end - start); + + /* if there's another name to come, write a separator */ + if (i + 1 < cnt) + { + /* + * write just "and" to separate two items; write "," + * between items in lists of more than two, with ",and" + * between the last two items + */ + write_ifiction_z(&ctx, + cnt == 2 ? " and " : + i + 2 < cnt ? ", " : ", and "); + } + } + + /* end the tag */ + write_ifiction_z(&ctx, "\n"); + } + + /* end the biblio section */ + write_ifiction_z(&ctx, " \n"); + + /* if there's cover art, add its information */ + if (find_cover_art(story_file, extent, 0, &art_fmt, &art_wid, &art_ht) + && (art_fmt == PNG_COVER_FORMAT || art_fmt == JPEG_COVER_FORMAT)) + { + char buf[200]; + + sprintf(buf, + " \n" + " %s\n" + " %lu\n" + " %lu\n" + " \n", + art_fmt == PNG_COVER_FORMAT ? "png" : "jpg", + (long)art_ht, (long)art_wid); + + write_ifiction_z(&ctx, buf); + } + + /* if there's an author email, include it */ + if (author != 0 || url != 0) + { + const char *p; + size_t rem; + int i; + + /* open the section */ + write_ifiction_z(&ctx, " \n"); + + /* add the author email, if provided */ + if (author != 0) + { + /* write the email list */ + for (i = 0, p = author->val, rem = author->val_len ; ; ++i) + { + const char *start; + + /* skip to the next email address */ + for ( ; rem != 0 && *p != '<' ; ++p, --rem) ; + + /* if we didn't find an email address, we're done */ + if (rem == 0) + break; + + /* find the matching '>' */ + for (++p, --rem, start = p ; rem != 0 && *p != '>' ; + ++p, --rem) ; + + /* + * if this is the first one, open the section; otherwise, + * add a comma + */ + if (i == 0) + write_ifiction_z(&ctx, " "); + else + write_ifiction_z(&ctx, ","); + + /* write this address */ + write_ifiction(&ctx, start, p - start); + + /* + * skip the closing bracket, if there is one; if we're out + * of string, we're done + */ + if (rem != 0) + ++p, --rem; + else + break; + } + + /* if we found any emails to write, end the section */ + if (i != 0) + write_ifiction_z(&ctx, "\n"); + } + + /* if there's a URL, add it */ + if (url != 0) + { + write_ifiction_z(&ctx, " "); + write_ifiction(&ctx, url->val, url->val_len); + write_ifiction_z(&ctx, "\n"); + } + + /* close the section */ + write_ifiction_z(&ctx, " \n"); + } + + /* add the tads-specific section */ + write_ifiction_z(&ctx, " \n"); + + write_ifiction_xlat(&ctx, 6, "Version", "version", 0); + write_ifiction_xlat(&ctx, 6, "ReleaseDate", "releasedate", 0); + write_ifiction_xlat(&ctx, 6, "PresentationProfile", + "presentationprofile", 0); + write_ifiction_xlat(&ctx, 6, "Byline", "byline", 0); + + write_ifiction_z(&ctx, " \n"); + + /* close the story section and the main body */ + write_ifiction_z(&ctx, " \n\n"); + + /* add the null terminator */ + write_ifiction(&ctx, "", 1); + + /* return the total output size */ + return ctx.total_size; +} + +/* ------------------------------------------------------------------------ */ +/* + * Check a data block to see if it starts with the given signature. + */ +int tads_match_sig(const void *buf, int32 len, const char *sig) +{ + /* note the length of the signature string */ + size_t sig_len = strlen(sig); + + /* if matches if the buffer starts with the signature string */ + return (len >= (int32)sig_len && memcmp(buf, sig, sig_len) == 0); +} + + +/* ------------------------------------------------------------------------ */ +/* + * portable-to-native format conversions + */ +#define osbyte(p, ofs) \ + (*(((unsigned char *)(p)) + (ofs))) + +#define osrp1(p) \ + ((unsigned int)osbyte(p, 0)) + +#define osrp2(p) \ + ((unsigned int)osbyte(p, 0) \ + + ((unsigned int)osbyte(p, 1) << 8)) + +#define osrp4(p) \ + (((unsigned long)osbyte(p, 0)) \ + + (((unsigned long)osbyte(p, 1)) << 8) \ + + (((unsigned long)osbyte(p, 2)) << 16) \ + + (((unsigned long)osbyte(p, 3)) << 24)) + + +/* ------------------------------------------------------------------------ */ +/* + * Parse a game file and retrieve the GameInfo data. Returns the head of a + * linked list of valinfo entries. + */ +static valinfo *parse_game_info(const void *story_file, int32 story_len, + int *tads_version) +{ + resinfo res; + const char *p; + int32 rem; + valinfo *val_head = 0; + + /* + * first, find the GameInfo resource - if it's not there, there's no + * game information to parse + */ + if (!find_resource(story_file, story_len, "GameInfo.txt", &res)) + return 0; + + /* if the caller wants the TADS version number, hand it back */ + if (tads_version != 0) + *tads_version = res.tads_version; + + /* parse the data */ + for (p = res.ptr, rem = res.len ; rem != 0 ; ) + { + const char *name_start; + size_t name_len; + const char *val_start; + valinfo *val; + const char *inp; + int32 inlen; + char *outp; + + /* skip any leading whitespace */ + while (rem != 0 && u_isspace(*p)) + ++p, --rem; + + /* if the line starts with '#', it's a comment, so skip it */ + if (rem != 0 && *p == '#') + { + skip_to_next_line(&p, &rem); + continue; + } + + /* we must have the start of a name - note it */ + name_start = p; + + /* skip ahead to a space or colon */ + while (rem != 0 && *p != ':' && !u_ishspace(*p)) + nextc(&p, &rem); + + /* note the length of the name */ + name_len = p - name_start; + + /* skip any whitespace before the presumed colon */ + while (rem != 0 && u_ishspace(*p)) + nextc(&p, &rem); + + /* if we're not at a colon, the line is ill-formed, so skip it */ + if (rem == 0 || *p != ':') + { + /* skip the entire line, and go back for the next one */ + skip_to_next_line(&p, &rem); + continue; + } + + /* skip the colon and any whitespace immediately after it */ + for (nextc(&p, &rem) ; rem != 0 && u_ishspace(*p) ; nextc(&p, &rem)) ; + + /* note where the value starts */ + val_start = p; + + /* + * Scan the value to get its length. The value runs from here to + * the next newline that's not followed immediately by a space. + */ + while (rem != 0) + { + const char *nl; + int32 nlrem; + + /* skip to the next line */ + skip_to_next_line(&p, &rem); + + /* if we're at eof, we can stop now */ + if (rem == 0) + break; + + /* note where this line starts */ + nl = p; + nlrem = rem; + + /* + * if we're at a non-whitespace character, it's definitely not + * a continuation line + */ + if (!u_ishspace(*p)) + break; + + /* + * check for spaces followed by a non-space character - this + * would signify a continuation line + */ + for ( ; rem != 0 && u_ishspace(*p) ; nextc(&p, &rem)) ; + if (rem == 0 || u_isnl(p, rem)) + { + /* + * we're at end of file, we found a line with nothing but + * whitespace, so this isn't a continuation line; go back + * to the start of this line and end the value here + */ + p = nl; + rem = nlrem; + break; + } + + /* + * We found whitespace followed by non-whitespace, so this is a + * continuation line. Keep going for now. + */ + } + + /* remove any trailing newlines */ + while (p > val_start) + { + /* move back one character */ + prevc(&p, &rem); + + /* + * if it's a newline, keep going; otherwise, keep this + * character and stop trimming + */ + if (!u_isnl(p, rem)) + { + nextc(&p, &rem); + break; + } + } + + /* + * Allocate a new value entry. Make room for the entry itself plus + * a copy of the value. We don't need to make a copy of the name, + * since we can just use the original copy from the story file + * buffer. We do need a copy of the value because we might need to + * transform it slightly, to remove newlines and leading spaces on + * continuation lines. + */ + val = (valinfo *)malloc(sizeof(valinfo) + (p - val_start)); + + /* link it into our list */ + val->nxt = val_head; + val_head = val; + + /* point the name directly to the name in the buffer */ + val->name = name_start; + val->name_len = name_len; + + /* point the value to the space allocated along with the valinfo */ + val->val = (char *)(val + 1); + + /* store the name, removing newlines and continuation-line spaces */ + for (outp = val->val, inp = val_start, inlen = p - val_start ; + inlen != 0 ; ) + { + const char *l; + + /* find the next newline */ + for (l = inp ; inlen != 0 && !u_isnl(inp, inlen) ; + nextc(&inp, &inlen)) ; + + /* copy this line to the output */ + memcpy(outp, l, inp - l); + outp += inp - l; + + /* if we're out of input, we're done */ + if (inlen == 0) + break; + + /* we're at a newline: replace it with a space in the output */ + *outp++ = ' '; + + /* skip the newline and subsequent whitespace in the input */ + for (skip_newline(&inp, &inlen) ; + inlen != 0 && u_ishspace(*inp) ; nextc(&inp, &inlen)) ; + } + + /* set the length of the parsed value */ + val->val_len = outp - val->val; + + /* skip to the next line and continue parsing */ + skip_to_next_line(&p, &rem); + } + + /* return the head of the linked list of value entries */ + return val_head; +} +static int my_memicmp(const void *aa, const void *bb, int l) +{ + int s=0,i; + char *a=(char *) aa; + char *b=(char *) bb; + for(i=0;inxt) + { + /* if this one matches the key we're looking for, return it */ + if (p->name_len == key_len && my_memicmp(p->name, key, key_len) == 0) + return p; + } + + /* no luck */ + return 0; +} + +/* ------------------------------------------------------------------------ */ +/* + * Delete a valinfo list obtained from parse_game_info() + */ +static void delete_valinfo_list(valinfo *head) +{ + /* keep going until we run out of entries */ + while (head != 0) + { + /* remember the next entry, before we delete this one */ + valinfo *nxt = head->nxt; + + /* delete this one */ + free(head); + + /* move on to the next one */ + head = nxt; + } +} + +/* ------------------------------------------------------------------------ */ +/* + * Find the cover art resource. We'll look for CoverArt.jpg and + * CoverArt.png, in that order. + */ +static int find_cover_art(const void *story_file, int32 story_len, + resinfo *resp, int32 *image_format, + int32 *width, int32 *height) +{ + resinfo res; + int32 x, y; + + /* if they didn't want the resource info, provide a placeholder */ + if (resp == 0) + resp = &res; + + /* look for CoverArt.jpg first */ + if (find_resource(story_file, story_len, "CoverArt.jpg", resp)) + { + /* get the width and height */ + if (!get_jpeg_dim(resp->ptr, resp->len, &x, &y)) + return FALSE; + + /* hand back the width and height if it was requested */ + if (width != 0) + *width = x; + if (height != 0) + *height = y; + + /* tell them it's a JPEG image */ + if (image_format != 0) + *image_format = JPEG_COVER_FORMAT; + + /* indicate success */ + return TRUE; + } + + /* look for CoverArt.png second */ + if (find_resource(story_file, story_len, "CoverArt.png", resp)) + { + /* get the width and height */ + if (!get_png_dim(resp->ptr, resp->len, &x, &y)) + return FALSE; + + /* hand back the width and height if it was requested */ + if (width != 0) + *width = x; + if (height != 0) + *height = y; + + /* tell them it's a PNG image */ + if (image_format != 0) + *image_format = PNG_COVER_FORMAT; + + /* indicate success */ + return TRUE; + } + + /* didn't find it */ + return FALSE; +} + +/* ------------------------------------------------------------------------ */ +/* + * Find a resource in a TADS 2 or 3 story file that's been loaded into + * memory. On success, fills in the offset and size of the resource and + * returns TRUE; if the resource isn't found, returns FALSE. + */ +static int find_resource(const void *story_file, int32 story_len, + const char *resname, resinfo *info) +{ + /* if there's no file, there's no resource */ + if (story_file == 0) + return FALSE; + + /* check for tads 2 */ + if (tads_match_sig(story_file, story_len, T2_SIGNATURE)) + { + info->tads_version = 2; + return t2_find_res(story_file, story_len, resname, info); + } + + /* check for tads 3 */ + if (tads_match_sig(story_file, story_len, T3_SIGNATURE)) + { + info->tads_version = 3; + return t3_find_res(story_file, story_len, resname, info); + } + + /* it's not one of ours */ + return FALSE; +} + +/* ------------------------------------------------------------------------ */ +/* + * Find a resource in a tads 2 game file + */ +static int t2_find_res(const void *story_file, int32 story_len, + const char *resname, resinfo *info) +{ + const char *basep = (const char *)story_file; + const char *endp = basep + story_len; + const char *p; + size_t resname_len; + + /* note the length of the name we're seeking */ + resname_len = strlen(resname); + + /* + * skip past the tads 2 file header (13 bytes for the signature, 7 + * bytes for the version header, 2 bytes for the flags, 26 bytes for + * the timestamp) + */ + p = basep + 13 + 7 + 2 + 26; + + /* + * scan the sections in the file; stop on $EOF, and skip everything + * else but HTMLRES, which is the section type that + */ + while (p < endp) + { + unsigned long endofs; + + /* + * We're pointing to a section block header, which looks like this: + * + *. type-length + *. type-name + *. next-section-address + */ + + /* read the ending offset */ + endofs = osrp4(p + 1 + osrp1(p)); + + /* check the type */ + if (p[0] == 7 && memcmp(p + 1, "HTMLRES", 7) == 0) + { + unsigned long found_ofs; + int found; + unsigned long entry_cnt; + + /* we haven't found the resource yet */ + found = FALSE; + + /* + * It's a multimedia resource block. Skip the section block + * header and look at the index table - the index table + * consists of a uint32 giving the number of entries, followed + * by a reserved uint32, followed by the entries. + */ + p += 12; + entry_cnt = osrp4(p); + + /* skip to the first index entry */ + p += 8; + + /* scan the index entries */ + for ( ; entry_cnt != 0 ; --entry_cnt) + { + unsigned long res_ofs; + unsigned long res_siz; + size_t name_len; + + /* + * We're at the next index entry, which looks like this: + * + *. resource-address (bytes from end of index) + *. resource-length (in bytes) + *. name-length + *. name + */ + res_ofs = osrp4(p); + res_siz = osrp4(p + 4); + name_len = osrp2(p + 8); + p += 10; + + /* check for a match to the name we're looking for */ + if (name_len == resname_len + && my_memicmp(resname, p, name_len) == 0) + { + /* + * it's the one we want - note its resource location + * and size, but keep scanning for now, since we need + * to find the end of the index before we'll know where + * the actual resources begin + */ + found = TRUE; + found_ofs = res_ofs; + info->len = res_siz; + } + + /* skip this one's name */ + p += name_len; + } + + /* + * if we found our resource, the current seek position is the + * base of the offset we found in the directory; so we can + * finally fix up the offset to give the actual file location + * and return the result + */ + if (found) + { + /* fix up the offset with the actual file location */ + info->ptr = p + found_ofs; + + /* tell the caller we found it */ + return TRUE; + } + } + else if (p[0] == 4 && memcmp(p + 1, "$EOF", 4) == 0) + { + /* + * that's the end of the file - we've finished without finding + * the resource, so return failure + */ + return FALSE; + } + + /* move to the next section */ + p = basep + endofs; + } + + /* + * reached EOF without an $EOF marker - file must be corrupted; return + * 'not found' + */ + return FALSE; +} + +/* ------------------------------------------------------------------------ */ +/* + * Find a resource in a T3 image file + */ +static int t3_find_res(const void *story_file, int32 story_len, + const char *resname, resinfo *info) +{ + const char *basep = (const char *)story_file; + const char *endp = basep + story_len; + const char *p; + size_t resname_len; + + /* note the length of the name we're seeking */ + resname_len = strlen(resname); + + /* + * skip the file header - 11 bytes for the signature, 2 bytes for the + * format version, 32 reserved bytes, and 24 bytes for the timestamp + */ + p = basep + 11 + 2 + 32 + 24; + + /* scan the data blocks */ + while (p < endp) + { + unsigned long siz; + + /* + * We're at the next block header, which looks like this: + * + *. type-name + *. block-size + *. flags + */ + + /* get the block size */ + siz = osrp4(p + 4); + + /* check the type */ + if (memcmp(p, "MRES", 4) == 0) + { + unsigned int entry_cnt; + unsigned int i; + const char *blockp; + + /* skip the header */ + p += 10; + + /* + * remember the location of the base of the block - the data + * seek location for each index entry is given as an offset + * from this location + */ + blockp = p; + + /* the first thing in the table is the number of entries */ + entry_cnt = osrp2(p); + p += 2; + + /* read the entries */ + for (i = 0 ; i < entry_cnt ; ++i) + { + unsigned long entry_ofs; + unsigned long entry_siz; + size_t entry_name_len; + char namebuf[256]; + char *xp; + size_t xi; + + /* + * Parse this index entry: + * + *. address (as offset from the block base) + *. size (in bytes) + *. name-length + *. name (all bytes XORed with 0xFF) + */ + entry_ofs = osrp4(p); + entry_siz = osrp4(p + 4); + entry_name_len = (unsigned char)p[8]; + + /* unmask the name */ + memcpy(namebuf, p + 9, resname_len); + for (xi = resname_len, xp = namebuf ; xi != 0 ; --xi) + *xp++ ^= 0xFF; + + /* if this is the one we're looking for, return it */ + if (entry_name_len == resname_len + && my_memicmp(resname, namebuf, resname_len) == 0) + { + /* + * fill in the return information - note that the entry + * offset given in the header is an offset from data + * block's starting location, so fix this up to an + * absolute seek location for the return value + */ + info->ptr = blockp + entry_ofs; + info->len = entry_siz; + + /* return success */ + return TRUE; + } + + /* skip this entry (header + name length) */ + p += 9 + entry_name_len; + } + + /* + * if we got this far, we didn't find the name; so skip past + * the MRES section by adding the section length to the base + * pointer, and resume the main file scan + */ + p = blockp + siz; + } + else if (memcmp(p, "EOF ", 4) == 0) + { + /* + * end of file - we've finished without finding the resource, + * so return failure + */ + return FALSE; + } + else + { + /* + * we don't care about anything else - just skip this block and + * keep going; to skip the block, simply seek ahead past the + * block header and then past the block's contents, using the + * size given the in block header + */ + p += siz + 10; + } + } + + /* + * reached EOF without an EOF marker - file must be corrupted; return + * 'not found' + */ + return FALSE; +} + +/* ------------------------------------------------------------------------ */ +/* + * JPEG and PNG information extraction (based on the versions in + * babel_story_functions.c) + */ +static int get_jpeg_dim(const void *img, int32 extent, + int32 *xout, int32 *yout) +{ + const unsigned char *dp=(const unsigned char *) img; + const unsigned char *ep=dp+extent; + unsigned int t1, t2, w, h; + + t1 = *dp++; + t2 = *dp++; + if (t1 != 0xff || t2 != 0xD8) + return FALSE; + + while(1) + { + if (dp>ep) return FALSE; + for(t1=*(dp++);t1!=0xff;t1=*(dp++)) if (dp>ep) return FALSE; + do { t1=*(dp++); if (dp>ep) return FALSE;} while (t1 == 0xff); + + if ((t1 & 0xF0) == 0xC0 && !(t1==0xC4 || t1==0xC8 || t1==0xCC)) + { + dp+=3; + if (dp>ep) return FALSE; + h=*(dp++) << 8; + if (dp>ep) return FALSE; + h|=*(dp++); + if (dp>ep) return FALSE; + w=*(dp++) << 8; + if (dp>ep) return FALSE; + w|=*(dp); + + *xout = w; + *yout = h; + return TRUE; + } + else if (t1==0xD8 || t1==0xD9) + break; + else + { + int l; + + if (dp>ep) return FALSE; + l=*(dp++) << 8; + if (dp>ep) return FALSE; + l|= *(dp++); + l-=2; + dp+=l; + if (dp>ep) return FALSE; + } + } + return FALSE; +} + +static int32 png_read_int(const unsigned char *mem) +{ + int32 i4 = mem[0], + i3 = mem[1], + i2 = mem[2], + i1 = mem[3]; + return i1 | (i2<<8) | (i3<<16) | (i4<<24); +} + + +static int get_png_dim(const void *img, int32 extent, + int32 *xout, int32 *yout) +{ + const unsigned char *dp=(const unsigned char *)img; + + if (extent<33 || + !(dp[0]==137 && dp[1]==80 && dp[2]==78 && dp[3]==71 && + dp[4]==13 && dp[5] == 10 && dp[6] == 26 && dp[7]==10)|| + !(dp[12]=='I' && dp[13]=='H' && dp[14]=='D' && dp[15]=='R')) + return FALSE; + + *xout = png_read_int(dp+16); + *yout = png_read_int(dp+20); + return TRUE; +} + +/* ------------------------------------------------------------------------ */ +/* + * Testing main() - this implements a set of unit tests on the tads + * version. + */ + +#ifdef TADS_TEST + +#include "babel_handler.h" + +void main(int argc, char **argv) +{ + FILE *fp; + int32 siz; + void *buf; + valinfo *head; + int32 rv; + int tadsver; + char outbuf[TREATY_MINIMUM_EXTENT]; + + /* check arguments */ + if (argc != 2) + { + printf("usage: tads \n"); + exit(1); + } + + /* initialize the babel subsystems */ + babel_init(argv[1]); + + /* open the story file */ + if ((fp = fopen(argv[1], "rb")) == 0) + { + printf("error opening input file\n"); + exit(2); + } + + /* check the file size */ + fseek(fp, 0, SEEK_END); + siz = ftell(fp); + fseek(fp, 0, SEEK_SET); + + /* allocate space for it */ + if ((buf = malloc(siz)) == 0) + { + printf("error allocating space to load file\n"); + exit(2); + } + + /* load it */ + if ((int32)fread(buf, 1, siz, fp) != siz) + { + printf("error reading file\n"); + exit(2); + } + + /* done with the file */ + fclose(fp); + + + + /* ===== test 1 - basic parse_game_info() test ===== */ + + /* parse the gameinfo record and print the results */ + if ((head = parse_game_info(buf, siz, &tadsver)) != 0) + { + valinfo *val; + + printf("found GameInfo - tads major version = %d\n", tadsver); + for (val = head ; val != 0 ; val = val->nxt) + { + printf("%.*s=[%.*s]\n", + (int)val->name_len, val->name, + (int)val->val_len, val->val); + } + printf("\n"); + } + else + printf("no GameInfo found\n\n"); + + + + /* ===== test 2 - test the get_story_file_IFID generator ===== */ + rv = tads_get_story_file_IFID(buf, siz, outbuf, TREATY_MINIMUM_EXTENT); + if (rv == 1) + printf("IFID = [%s]\n\n", outbuf); + else + printf("IFID return code = %ld\n", rv); + + + + /* ===== test 3 - test the ifiction synthesizer ===== */ + if ((rv = tads_get_story_file_metadata_extent(buf, siz)) > 0) + { + char *ifbuf; + + /* try allocating the space */ + if ((ifbuf = malloc((size_t)rv)) != 0) + { + /* synthesize the story file */ + rv = tads_get_story_file_metadata(buf, siz, ifbuf, rv); + if (rv > 0) + printf("ifiction metadata:\n=====\n%.*s\n=====\n\n", + (int)rv, ifbuf); + else + printf("tads_get_story_file_metadata result = %ld\n", rv); + } + else + printf("unable to allocate %ld bytes for metadata record\n", rv); + } + else + printf("tads_get_story_file_metadata_extent result code = %ld\n", rv); + + + /* free the loaded story file buffer */ + free(buf); +} + + +#endif TADS_TEST + diff --git a/babel/tads.h b/babel/tads.h new file mode 100644 index 0000000..296b91d --- /dev/null +++ b/babel/tads.h @@ -0,0 +1,40 @@ +/* + * tads.h - Treaty of Babel common declarations for tads2 and tads3 modules + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file may + * render it noncompliant with the Treaty of Babel + * + * Modified + *. 04/18/2006 MJRoberts - creation + */ + +#ifndef TADS_H +#define TADS_H + +/* match a TADS file signature */ +int tads_match_sig(const void *buf, int32 len, const char *sig); + +/* get the IFID for a tads story file */ +int32 tads_get_story_file_IFID(void *story_file, int32 extent, + char *output, int32 output_extent); + +/* get the synthesized iFiction record from a tads story file */ +int32 tads_get_story_file_metadata(void *story_file, int32 extent, + char *buf, int32 bufsize); + +/* get the size of the synthesized iFiction record for a tads story file */ +int32 tads_get_story_file_metadata_extent(void *story_file, int32 extent); + +/* get the cover art from a tads story file */ +int32 tads_get_story_file_cover(void *story_file, int32 extent, + void *buf, int32 bufsize); + +/* get the size of the cover art from a tads story file */ +int32 tads_get_story_file_cover_extent(void *story_file, int32 extent); + +/* get the image format (jpeg, png) of the covert art in a tads story file */ +int32 tads_get_story_file_cover_format(void *story_file, int32 extent); + +#endif /* TADS_H */ diff --git a/babel/tads2.c b/babel/tads2.c new file mode 100644 index 0000000..87c1fcc --- /dev/null +++ b/babel/tads2.c @@ -0,0 +1,100 @@ +/* + * tads2.c - Treaty of Babel module for Tads 2 files + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file may + * render it noncompliant with the Treaty of Babel + * + * Modified + *. 04/15/2006 LRRaszewski - Separated tads2.c and tads3.c + *. 04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions + *. 04/08/2006 MJRoberts - initial implementation + */ + +#define FORMAT tads2 +#define HOME_PAGE "http://www.tads.org" +#define FORMAT_EXT ".gam" + + +#include "treaty_builder.h" +#include "tads.h" + +#define T2_SIGNATURE "TADS2 bin\012\015\032" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/* + * get a story file's IFID + */ +static int32 get_story_file_IFID(void *story_file, int32 extent, + char *output, int32 output_extent) +{ + /* use the common tads IFID extractor/generator */ + return tads_get_story_file_IFID(story_file, extent, + output, output_extent); +} + +/* + * determine if a given story file is one of ours + */ +static int32 claim_story_file(void *story_file, int32 extent) +{ + /* check our signature */ + if (tads_match_sig(story_file, extent, T2_SIGNATURE)) + return VALID_STORY_FILE_RV; + + /* not one of ours */ + return INVALID_STORY_FILE_RV; +} + +/* + * Get the size of the iFiction metadata for the game + */ +static int32 get_story_file_metadata_extent(void *story_file, int32 extent) +{ + /* use the common tads iFiction synthesizer */ + return tads_get_story_file_metadata_extent(story_file, extent); +} + +/* + * Get the iFiction metadata for the game + */ +static int32 get_story_file_metadata(void *story_file, int32 extent, + char *buf, int32 bufsize) +{ + /* use the common tads iFiction synthesizer */ + return tads_get_story_file_metadata(story_file, extent, buf, bufsize); +} + +static int32 get_story_file_cover_extent(void *story_file, int32 story_len) +{ + /* use the common tads cover file extractor */ + return tads_get_story_file_cover_extent(story_file, story_len); +} + +/* + * Get the format of the cover art + */ +static int32 get_story_file_cover_format(void *story_file, int32 story_len) +{ + /* use the common tads cover file extractor */ + return tads_get_story_file_cover_format(story_file, story_len); +} + +/* + * Get the cover art data + */ +static int32 get_story_file_cover(void *story_file, int32 story_len, + void *outbuf, int32 output_extent) +{ + /* use the common tads cover file extractor */ + return tads_get_story_file_cover(story_file, story_len, + outbuf, output_extent); +} + diff --git a/babel/tads3.c b/babel/tads3.c new file mode 100644 index 0000000..23d8fb5 --- /dev/null +++ b/babel/tads3.c @@ -0,0 +1,100 @@ +/* + * tads3.c - Treaty of Babel module for Tads 3 files + * + * This file depends on treaty_builder.h + * + * This file is public domain, but note that any changes to this file may + * render it noncompliant with the Treaty of Babel + * + * Modified + *. 04/15/2006 LRRaszewski - Separated tads2.c and tads3.c + *. 04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions + *. 04/08/2006 MJRoberts - initial implementation + */ + +#define FORMAT tads3 +#define HOME_PAGE "http://www.tads.org" +#define FORMAT_EXT ".t3" + + +#include "treaty_builder.h" +#include "tads.h" + +#define T3_SIGNATURE "T3-image\015\012\032" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/* + * get a story file's IFID + */ +static int32 get_story_file_IFID(void *story_file, int32 extent, + char *output, int32 output_extent) +{ + /* use the common tads IFID extractor/generator */ + return tads_get_story_file_IFID(story_file, extent, + output, output_extent); +} + +/* + * determine if a given story file is one of ours + */ +static int32 claim_story_file(void *story_file, int32 extent) +{ + /* check our signature */ + if (tads_match_sig(story_file, extent, T3_SIGNATURE)) + return VALID_STORY_FILE_RV; + + /* not one of ours */ + return INVALID_STORY_FILE_RV; +} + +/* + * Get the size of the iFiction metadata for the game + */ +static int32 get_story_file_metadata_extent(void *story_file, int32 extent) +{ + /* use the common tads iFiction synthesizer */ + return tads_get_story_file_metadata_extent(story_file, extent); +} + +/* + * Get the iFiction metadata for the game + */ +static int32 get_story_file_metadata(void *story_file, int32 extent, + char *buf, int32 bufsize) +{ + /* use the common tads iFiction synthesizer */ + return tads_get_story_file_metadata(story_file, extent, buf, bufsize); +} + +static int32 get_story_file_cover_extent(void *story_file, int32 story_len) +{ + /* use the common tads cover file extractor */ + return tads_get_story_file_cover_extent(story_file, story_len); +} + +/* + * Get the format of the cover art + */ +static int32 get_story_file_cover_format(void *story_file, int32 story_len) +{ + /* use the common tads cover file extractor */ + return tads_get_story_file_cover_format(story_file, story_len); +} + +/* + * Get the cover art data + */ +static int32 get_story_file_cover(void *story_file, int32 story_len, + void *outbuf, int32 output_extent) +{ + /* use the common tads cover file extractor */ + return tads_get_story_file_cover(story_file, story_len, + outbuf, output_extent); +} + diff --git a/babel/treaty.h b/babel/treaty.h new file mode 100644 index 0000000..7155553 --- /dev/null +++ b/babel/treaty.h @@ -0,0 +1,91 @@ +/* treaty.h Header file for Treaty of Babel compliant format modules + * By L. Ross Raszewski + * Version 3b + * + * This file is public domain, but please note that derived versions + * may not not be compliant with the Treaty of Babel. + * + * It would be wise for derived works to change the value of + * TREATY_COMPLIANCE to reflect deviations + */ + +#ifndef TREATY_H + +#define TREATY_H + +#define TREATY_COMPLIANCE "Treaty of Babel revision 7" +#define TREATY_VERSION "r7" + +/* return codes */ +#define NO_REPLY_RV 0 +#define INVALID_STORY_FILE_RV -1 +#define UNAVAILABLE_RV -2 +#define INVALID_USAGE_RV -3 +#define INCOMPLETE_REPLY_RV -4 +#define VALID_STORY_FILE_RV 1 + +#define PNG_COVER_FORMAT 1 +#define JPEG_COVER_FORMAT 2 + +/* Treaty bitmasks. These are not required by the treaty, but are here + as a convenience. +*/ +#define TREATY_SELECTOR_INPUT 0x100 +#define TREATY_SELECTOR_OUTPUT 0x200 +#define TREATY_SELECTOR_NUMBER 0xFF + +#define TREATY_CONTAINER_SELECTOR 0x400 + +/* Treaty selectors */ +#define GET_HOME_PAGE_SEL 0x201 +#define GET_FORMAT_NAME_SEL 0x202 +#define GET_FILE_EXTENSIONS_SEL 0x203 +#define CLAIM_STORY_FILE_SEL 0x104 +#define GET_STORY_FILE_METADATA_EXTENT_SEL 0x105 +#define GET_STORY_FILE_COVER_EXTENT_SEL 0x106 +#define GET_STORY_FILE_COVER_FORMAT_SEL 0x107 +#define GET_STORY_FILE_IFID_SEL 0x308 +#define GET_STORY_FILE_METADATA_SEL 0x309 +#define GET_STORY_FILE_COVER_SEL 0x30A +#define GET_STORY_FILE_EXTENSION_SEL 0x30B + +/* Container selectors */ +#define CONTAINER_GET_STORY_FORMAT_SEL 0x710 +#define CONTAINER_GET_STORY_EXTENT_SEL 0x511 +#define CONTAINER_GET_STORY_FILE_SEL 0x711 + + + + +/* Other magic size limits */ +#define TREATY_MINIMUM_EXTENT 512 + + +#include + +/* 32-bit integer types */ +#ifndef VAX +#if SCHAR_MAX >= 0x7FFFFFFFL && SCHAR_MIN <= -0x7FFFFFFFL + typedef signed char int32; +#elif SHRT_MAX >= 0x7FFFFFFFL && SHRT_MIN <= -0x7FFFFFFFL + typedef signed short int int32; +#elif INT_MAX >= 0x7FFFFFFFL && INT_MIN <= -0x7FFFFFFFL + typedef signed int int32; +#elif LONG_MAX >= 0x7FFFFFFFL && LONG_MIN <= -0x7FFFFFFFL + typedef signed long int int32; +#else +#error No type large enough to support 32-bit integers. +#endif +#else + /* VAX C does not provide these limit constants, contrary to ANSI */ + typedef int int32; +#endif + + + +/* Pointer to treaty function. Treaty functions must follow this prototype */ + +typedef int32 (*TREATY)(int32 selector, void *, int32, void *, int32); + +#endif + diff --git a/babel/treaty_builder.h b/babel/treaty_builder.h new file mode 100644 index 0000000..d52ae3e --- /dev/null +++ b/babel/treaty_builder.h @@ -0,0 +1,180 @@ +/* treaty_builder.h common macros to build a treaty module + * + * 2006 By L. Ross Raszewski + * + * This file is public domain, but be aware that any changes to it may + * cause it to cease to be compliant with the Treaty of Babel. + * + * This file depends on treaty.h + * + * The purpose of this file is to simplify the building of a treaty + * module. It automatically generates a generic treaty function. + * + * Usage: + * + * #define the following values: + * FORMAT The treaty name of the format + * HOME_PAGE A string containing the URL of the format home page + * FORMAT_EXT A string containing a comma separated list of common + * extensions for game files in this format + * NO_METADATA If the format does not support metadata + * NO_COVER If the format does not support cover art + * CUSTOM_EXTENSION If game files should not always use the first listed extension + * + * (Note: Formats which support metadata and cover art via a container should + * define NO_METADATA and NO_COVER as container support is handled separately) + * + * #include "treaty_builder.h" + * Define the following functions: + * static int32 get_story_file_IFID(void *, int32, char *, int32); + * static int32 claim_story_file(void *, int32); + * Define the following functions if NO_METADATA is not defined: + * static int32 get_story_file_metadata_extent(void *, int32); + * static int32 get_story_file_metadata(void *, int32, char *, int32); + * Define the following functions if NO_COVER is not defined + * static int32 get_story_file_cover_extent(void *, int32); + * static int32 get_story_file_cover_format(void *, int32); + * static int32 get_story_file_cover(void *, int32, void *, int32); + * Define the following if CUSTOM_EXTENSION is defined + * static int32 get_story_file_extension(void *, int32, char *, int32); + * + * The two-parameter functions take the story file and story file extent + * as parameters. The four-parameter ones also take the output + * buffer and its extent. They perform the corresponding task to the + * similarly-named selector. + * + * This file also defines the macro ASSERT_OUTPUT_SIZE(x) which + * returns INVALID_USAGE_RV if output_extent is less than x. + * + * #define CONTAINER_FORMAT before inclusion to generate a container + * module. A container module should define three additional functions: + * static int32 get_story_format(void *, int32, char *, int32); + * static int32 get_story_extent(void *, int32); + * static int32 get_story_file(void *, int32, void *, int32); + * + */ + +#ifndef TREATY_BUILDER +#define TREATY_BUILDER + +#include "treaty.h" +#include + +#define ASSERT_OUTPUT_SIZE(x) do { if (output_extent < (x)) return INVALID_USAGE_RV; } while (0) + +#ifndef NO_METADATA +static int32 get_story_file_metadata_extent(void *, int32); +static int32 get_story_file_metadata(void *, int32, char *, int32); +#endif +#ifndef NO_COVER +static int32 get_story_file_cover_extent(void *, int32); +static int32 get_story_file_cover_format(void *, int32); +static int32 get_story_file_cover(void *, int32, void *, int32); +#endif +static int32 get_story_file_IFID(void *, int32, char *, int32); +static int32 claim_story_file(void *, int32); +#ifdef CONTAINER_FORMAT +static int32 get_story_file(void *, int32, void *, int32); +static int32 get_story_format(void *, int32, char *, int32); +static int32 get_story_extent(void *, int32); +#endif +#ifdef CUSTOM_EXTENSION +static int32 get_story_file_extension(void *, int32, char *, int32); +#else +#include +static int32 get_story_file_extension(void *sf, int32 extent, char *out, int32 output_extent) +{ + int i; + + if (!sf || !extent) return INVALID_STORY_FILE_RV; + + for(i=0;FORMAT_EXT[i] && FORMAT_EXT[i]!=',';i++); + ASSERT_OUTPUT_SIZE(i+1); + memcpy(out,FORMAT_EXT,i); + out[i]=0; + return strlen(out); +} + +#endif + +#define TREATY_FUNCTION(X) DEEP_TREATY_FUNCTION(X) +#define DEEP_TREATY_FUNCTION(X) X ## _treaty +#define dSTRFRY(X) #X +#define STRFRY(X) dSTRFRY(X) + +int32 TREATY_FUNCTION(FORMAT)(int32 selector, + void *story_file, int32 extent, + void *output, int32 output_extent) +{ + int32 ll, csf; + if ((TREATY_SELECTOR_INPUT & selector) && + (csf=claim_story_file(story_file, extent)) +#include + +static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) +{ + int32 i,j; + char ser[7]; + char buffer[32]; + + + if (extent<0x1D) return INVALID_STORY_FILE_RV; + memcpy(ser, (char *) story_file+0x12, 6); + ser[6]=0; + /* Detect vintage story files */ + if (!(ser[0]=='8' || ser[0]=='9' || + (ser[0]=='0' && ser[1]>='0' && ser[1]<='5'))) + { + for(i=0;i 8 + ) return INVALID_STORY_FILE_RV; + for(i=4;i<=14;i+=2) + { + j=read_zint(sf+i); + if (j>extent || j < 0x40) return INVALID_STORY_FILE_RV; + } + + return VALID_STORY_FILE_RV; +} +static int32 get_story_file_extension(void *sf, int32 extent, char *out, int32 output_extent) +{ + int v; + if (!extent) return INVALID_STORY_FILE_RV; + v= ((char *) sf)[0]; + if (v>9) ASSERT_OUTPUT_SIZE(5); + else ASSERT_OUTPUT_SIZE(4); + sprintf(out,".z%d",v); + return 3+(v>9); + +} diff --git a/configure.ac b/configure.ac index 9e73d11..3fb5d01 100644 --- a/configure.ac +++ b/configure.ac @@ -137,6 +137,7 @@ AC_SUBST(CHIMARA_LIBS) PKG_CHECK_MODULES([TEST], [ gtk+-2.0 >= $GTK_REQUIRED_VERSION gmodule-2.0 >= $GLIB_REQUIRED_VERSION + libgda-4.0 ]) # GStreamer plugins needed to run library @@ -188,6 +189,7 @@ docs/reference/Makefile docs/reference/version.xml docs/reference/build-selector-table.pl po/Makefile.in +babel/Makefile ]) # Do it diff --git a/tests/Aotearoa.gblorb b/tests/Aotearoa.gblorb new file mode 100644 index 0000000..3db124a Binary files /dev/null and b/tests/Aotearoa.gblorb differ diff --git a/tests/Makefile.am b/tests/Makefile.am index 6d12176..c8ae301 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -10,7 +10,7 @@ TEST_PLUGIN_LIBTOOL_FLAGS = \ -export-symbols-regex "^glk_main$$" \ -rpath $(abs_builddir) -noinst_PROGRAMS = test-multisession glulxercise plugin-loader test-close +noinst_PROGRAMS = test-multisession glulxercise plugin-loader test-close babeltest test_multisession_SOURCES = test-multisession.c test_multisession_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) @@ -29,6 +29,10 @@ test_close_SOURCES = test-close.c test_close_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) test_close_LDADD = @TEST_LIBS@ $(top_builddir)/libchimara/libchimara.la +babeltest_SOURCES = babeltest.c +babeltest_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) +babeltest_LDADD = @TEST_LIBS@ $(top_builddir)/babel/libbabel_functions.la $(top_builddir)/babel/libbabel.la $(top_builddir)/babel/libifiction.la + noinst_LTLIBRARIES = first.la model.la gridtest.la splittest.la multiwin.la \ styletest.la soundtest.la test-userstyle.la fileio.la diff --git a/tests/babeltest.c b/tests/babeltest.c new file mode 100644 index 0000000..6daba85 --- /dev/null +++ b/tests/babeltest.c @@ -0,0 +1,177 @@ +#include "babel/babel_handler.h" + +#include +#include +#include +#include +#include +#include +#include + +typedef struct _metadata { + const gchar *element_name; + gchar *ifid; + gchar *title; + gchar *author; + gchar *year; +} metadata; + +void start_element( + GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer data, + GError **error) +{ + metadata *md = (metadata*) data; + md->element_name = element_name; + + if( !strcmp(element_name, "ifindex") ) { + md->ifid = g_strdup(""); + md->title = g_strdup(""); + md->author = g_strdup(""); + md->year = g_strdup(""); + } +} + +void text( + GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer data, + GError **error) +{ + metadata *md = (metadata*) data; + gchar *stripped_text; + + if( !strcmp(md->element_name, "ifid") ) { + stripped_text = g_strstrip( g_strndup(text, text_len) ); + md->ifid = g_strconcat(md->ifid, stripped_text, NULL); + g_free(stripped_text); + } + else if( !strcmp(md->element_name, "title") ) { + stripped_text = g_strstrip( g_strndup(text, text_len) ); + md->title = g_strconcat(md->title, stripped_text, NULL); + g_free(stripped_text); + } + else if( !strcmp(md->element_name, "author") ) { + stripped_text = g_strstrip( g_strndup(text, text_len) ); + md->author = g_strconcat(md->author, stripped_text, NULL); + g_free(stripped_text); + } + else if( !strcmp(md->element_name, "firstpublished") ) { + stripped_text = g_strstrip( g_strndup(text, text_len) ); + md->year = g_strconcat(md->year, stripped_text, NULL); + g_free(stripped_text); + } +} + +void end_element( + GMarkupParseContext *context, + const gchar *element_name, + gpointer data, + GError **error) +{ + if( !strcmp(element_name, "ifindex") ) { + metadata *md = (metadata*) data; + printf("IFID: %s\nTitle: %s\nAuthor: %s\nYear: %s\n", md->ifid, md->title, md->author, md->year); + } +} + +/* + * run a non SELECT command and stops if an error occurs + */ +void +run_sql_non_select(GdaConnection *cnc, const gchar *sql) +{ + GdaStatement *stmt; + GError *error = NULL; + gint nrows; + const gchar *remain; + GdaSqlParser *parser; + + parser = g_object_get_data(G_OBJECT(cnc), "parser"); + stmt = gda_sql_parser_parse_string(parser, sql, &remain, &error); + if(remain) + g_print ("REMAINS: %s\n", remain); + + nrows = gda_connection_statement_execute_non_select(cnc, stmt, NULL, NULL, &error); + if(nrows == -1) + g_error("NON SELECT error: %s\n", error && error->message ? error->message : "no detail"); + g_object_unref(stmt); +} + +int main(int argc, char **argv) { + if(argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + babel_init(argv[1]); + int len = babel_treaty(GET_STORY_FILE_METADATA_EXTENT_SEL, NULL, 0); + if(len == 0) { + printf("No metadata found.\n"); + babel_release(); + return 0; + } + + gchar *buffer = malloc(len * sizeof(gchar)); + babel_treaty(GET_STORY_FILE_METADATA_SEL, buffer, len); + g_strchomp(buffer); + len = strlen(buffer); + + metadata data; + GMarkupParser xml_parser = {start_element, end_element, text, NULL, NULL}; + GMarkupParseContext *context = g_markup_parse_context_new(&xml_parser, 0, &data, NULL); + + GError *err = NULL; + if( g_markup_parse_context_parse(context, buffer, len, &err) == FALSE ) { + fprintf(stderr, "Metadata parse failed: %s\n", err->message); + } + + free(buffer); + g_markup_parse_context_free(context); + babel_release(); + + // Open DB connection + GdaConnection *cnc; + GdaSqlParser *sql_parser; + + gda_init(); + cnc = gda_connection_open_from_string("SQLite", "DB_DIR=.;DB_NAME=library", NULL, GDA_CONNECTION_OPTIONS_NONE, &err); + if(!cnc) { + fprintf(stderr, "Could not open connection to SQLite database in library.db file: %s\n", err && err->message ? err->message : "No details"); + return 1; + } + + sql_parser = gda_connection_create_parser(cnc); + if(!sql_parser) // cnc does not provide its own parser, use default one + sql_parser = gda_sql_parser_new(); + + g_object_set_data_full(G_OBJECT(cnc), "parser", sql_parser, g_object_unref); + + // Create stories table + run_sql_non_select(cnc, "DROP TABLE IF EXISTS stories"); + run_sql_non_select(cnc, "CREATE TABLE stories (ifid text not null primary key, title text, author text, year integer)"); + + // Populate the table + GValue *v1, *v2, *v3, *v4; + v1 = gda_value_new_from_string(data.ifid, G_TYPE_STRING); + v2 = gda_value_new_from_string(data.title, G_TYPE_STRING); + v3 = gda_value_new_from_string(data.author, G_TYPE_STRING); + v4 = gda_value_new_from_string(data.year, G_TYPE_UINT); + + if( !gda_insert_row_into_table(cnc, "stories", &err, "ifid", v1, "title", v2, "author", v3, "year", v4, NULL) ) { + g_error("Could not INSERT data into the 'stories' table: %s\n", err && err->message ? err->message : "No details"); + return 1; + } + + gda_value_free(v1); + gda_value_free(v2); + gda_value_free(v3); + gda_value_free(v4); + + gda_connection_close(cnc); + return 0; +}