Index: class.c ================================================================== --- class.c +++ class.c @@ -875,11 +875,11 @@ #define Inst7bit() (peepcodes[ptr-1]<0x0080) #define Inst8bit() (peepcodes[ptr-1]<0x0100) #define AbbrevOp(x,y) case x: if(Inst7bit()) ChangeInst(+=0x1000|((y)<<4)); else AddInstF(x,tokent); break #define FlowPush(x) do{ if(flowdepth==64) ParseError("Too much flow control nesting\n"); flowop[flowdepth]=x; flowptr[flowdepth++]=ptr; }while(0) #define FlowPop(x) do{ if(!flowdepth || flowop[--flowdepth]!=(x)) ParseError("Flow control mismatch\n"); }while(0) -static int parse_instructions(int cla,int ptr,Hash*hash) { +static int parse_instructions(int cla,int ptr,Hash*hash,int compat) { int peep=ptr; int prflag=0; Class*cl=classes[cla]; int flowdepth=0; Uint16 flowop[64]; @@ -1005,40 +1005,33 @@ x=flowptr[flowdepth]; FlowPop(OP_BEGIN); AddInst2(OP_GOTO,flowptr[flowdepth]); cl->codes[x]=ptr; break; + case OP_FOR: + if(cl->uservars>=0x07FF || num_globals>=0x07FF) ParseError("Too many user variables\n"); + if(cla) x=cl->uservars++|0x2000; else x=num_globals++|0x2800; + AddInst2(OP_FOR,x); + FlowPush(OP_FOR); + peep=++ptr; + break; case OP_NEXT: FlowPop(OP_FOR); AddInst(OP_NEXT); cl->codes[flowptr[flowdepth]]=ptr; break; default: if(Tokenf(TF_ABNORMAL)) ParseError("Invalid instruction token\n"); + if(compat && Tokenf(TF_COMPAT)) ++tokenv; AddInstF(tokenv,tokent); } } else if(Tokenf(TF_FUNCTION)) { AddInst2(OP_FUNCTION,tokenv&0x3FFF); } else if(tokent==TF_OPEN) { nxttok(); if(Tokenf(TF_MACRO) || !Tokenf(TF_NAME)) ParseError("Invalid parenthesized instruction\n"); switch(tokenv) { - case OP_FOR: - nxttok(); - if(Tokenf(TF_MACRO|TF_EQUAL) || !Tokenf(TF_NAME)) ParseError("Global or local variable expected\n"); - if(tokenv==OP_LOCAL) { - if(!cla) ParseError("Cannot use local variable in a global definition\n"); - tokenv=look_hash(hash,LOCAL_HASH_SIZE,0x2000,0x27FF,cl->uservars+0x2000,"user local variables")?:(cl->uservars++)+0x2000; - } else if(tokenv<0x2000 || tokenv>0x2FFF) { - ParseError("Global or local variable expected\n"); - } - AddInst2(OP_FOR,tokenv); - FlowPush(OP_FOR); - peep=++ptr; - nxttok(); - if(tokent!=TF_CLOSE) ParseError("Unterminated (for)\n"); - break; case OP_POPUP: nxttok(); if(tokent!=TF_INT || tokenv<0 || tokenv>32) ParseError("Expected number from 0 to 32"); if(tokenv) AddInst2(OP_POPUPARGS,tokenv); else AddInst(OP_POPUP); nxttok(); @@ -1078,13 +1071,103 @@ } if(flowdepth) ParseError("Unterminated flow control blocks (%d levels)\n",flowdepth); cl->codes=realloc(cl->codes,ptr*sizeof(Uint16))?:cl->codes; return ptr; } + +static void dump_class(int cla,int endptr,const Hash*hash) { + const Class*cl=classes[cla]; + int i,j; + if(!cl) return; + printf("<<>>\n",cla); + printf(" Name: %c%s\n",cla?'$':'(',cla?cl->name:"Global)"); + printf(" Flags: 0x%02X 0x%04X\n",cl->cflags,cl->oflags); + printf(" Ht=%d Wt=%d Clm=%d Den=%d Vol=%d Str=%d Arr=%d Dep=%d\n",cl->height,cl->weight,cl->climb,cl->density,cl->volume,cl->strength,cl->arrivals,cl->departures); + printf(" Temp=%d M4=%d M5=%d M6=%d M7=%d Shape=%d Shov=%d Coll=%d Img=%d\n",cl->temperature,cl->misc4,cl->misc5,cl->misc6,cl->misc7,cl->shape,cl->shovable,cl->collisionLayers,cl->nimages); + printf(" Hard: %d %d %d %d\n",cl->hard[0],cl->hard[1],cl->hard[2],cl->hard[3]); + printf(" Sharp: %d %d %d %d\n",cl->sharp[0],cl->sharp[1],cl->sharp[2],cl->sharp[3]); + if(cl->nimages && cl->images) { + printf(" Images:"); + for(i=0;inimages;i++) printf(" %d%c",cl->images[i]&0x7FFF,cl->images[i]&0x8000?'*':'.'); + putchar('\n'); + } + if(cl->nmsg && cl->messages) { + printf(" Messages:\n"); + for(i=0;inmsg;i++) { + if(cl->messages[i]!=0xFFFF) { + if(i<256) printf(" %s: 0x%04X\n",standard_message_names[i],cl->messages[i]); + else printf(" #%s: 0x%04X\n",messages[i-256],cl->messages[i]); + } + } + } + if(hash && cl->uservars) { + printf(" Variables:\n"); + for(i=0;i=0x2000 && hash[i].id<0x3000) printf(" %%%s = 0x%04X\n",hash[i].txt,hash[i].id); + } + } + if(endptr && cl->codes) { + printf(" Codes:\n"); + for(i=0;icodes[i]); + } + putchar('\n'); + } + printf("---\n\n"); +} static void class_definition(int cla) { - + Hash*hash=calloc(LOCAL_HASH_SIZE,sizeof(Hash)); + Class*cl=classes[cla]; + int ptr=0; + int compat=0; + int i; + if(!hash) fatal("Allocation failed\n"); + if(!cl) fatal("Confusion of class definition somehow\n"); + if(cl->cflags&(CF_NOCLASS1|CF_NOCLASS2)) { + cl->cflags=0; + } else { + ParseError("Duplicate definition of class $%s\n",cl->name); + } + begin_label_stack(); + for(;;) { + nxttok(); + if(Tokenf(TF_EOF)) { + ParseError("Unexpected end of file\n"); + } else if(Tokenf(TF_MACRO)) { + ParseError("Unexpected macro token\n"); + } else if(Tokenf(TF_CLOSE)) { + break; + } else if(Tokenf(TF_OPEN)) { + nxttok(); + + } else if(Tokenf(TF_NAME)) { + switch(tokenv) { + case OP_PLAYER: cl->cflags|=CF_PLAYER; break; + case OP_INPUT: cl->cflags|=CF_INPUT; break; + case OP_COMPATIBLE: cl->cflags|=CF_COMPATIBLE; compat=1; break; + case OP_COMPATIBLE_C: cl->cflags|=CF_COMPATIBLE; break; + case OP_QUIZ: cl->cflags|=CF_QUIZ; break; + case OP_INVISIBLE: cl->oflags|=OF_INVISIBLE; break; + case OP_VISUALONLY: cl->oflags|=OF_VISUALONLY; break; + case OP_STEALTHY: cl->oflags|=OF_STEALTHY; break; + case OP_USERSTATE: cl->oflags|=OF_USERSTATE; break; + case OP_SHOVABLE: cl->shovable=0x55; break; + default: ParseError("Invalid directly inside of a class definition\n"); + } + } else { + ParseError("Invalid directly inside of a class definition\n"); + } + } + end_label_stack(classes[0]->codes,hash); + if(main_options['C']) dump_class(cla,ptr,hash); + if(main_options['H']) { + for(i=0;icodes,glolocalhash); } else if(Tokenf(TF_NAME)) { switch(tokenv) { case 0x4000 ... 0x7FFF: // Class definition class_definition(tokenv-0x4000); break; case 0x0200 ... 0x02FF: case 0xC000 ... 0xFFFF: // Default message handler begin_label_stack(); set_message_ptr(0,tokenv&0x8000?(tokenv&0x3FFF)+256:tokenv-0x0200,gloptr); - gloptr=parse_instructions(0,gloptr,glolocalhash); + gloptr=parse_instructions(0,gloptr,glolocalhash,0); end_label_stack(classes[0]->codes,glolocalhash); break; case 0x2800 ... 0x2FFF: // Define initial values of global variables i=tokenv-0x2800; nxttok(); @@ -1189,12 +1272,14 @@ ParseError("Invalid top level definition\n"); } } done: fclose(classfp); + if(main_options['C']) dump_class(0,gloptr,glolocalhash); if(main_options['H']) { for(i=0;i #include #include #include +#include +#include +#include #include #include "sqlite3.h" #include "smallxrm.h" #include "names.h" #include "quarks.h" @@ -25,12 +29,13 @@ "BEGIN;" "PRAGMA APPLICATION_ID(1296388936);" "PRAGMA RECURSIVE_TRIGGERS(1);" "CREATE TABLE IF NOT EXISTS `USERCACHEINDEX`(`ID` INTEGER PRIMARY KEY, `NAME` TEXT, `TIME` INT);" "CREATE TABLE IF NOT EXISTS `USERCACHEDATA`(`ID` INTEGER PRIMARY KEY, `FILE` INT, `LEVEL` INT, `NAME` TEXT, `OFFSET` INT, `DATA` BLOB, `USERSTATE` BLOB);" - "CREATE INDEX IF NOT EXISTS `USERCACHEDATA_I1` ON `USERCACHEDATA`(`FILE`, `LEVEL`) WHERE `LEVEL` IS NOT NULL;" + "CREATE INDEX IF NOT EXISTS `USERCACHEDATA_I1` ON `USERCACHEDATA`(`FILE`, `LEVEL`);" "CREATE TEMPORARY TABLE `PICTURES`(`ID` INTEGER PRIMARY KEY, `NAME` TEXT, `OFFSET` INT);" + "CREATE TEMPORARY TABLE `VARIABLES`(`ID` INTEGER PRIMARY KEY, `NAME` TEXT);" "COMMIT;" ; sqlite3*userdb; xrm_db*resourcedb; @@ -41,12 +46,14 @@ static const char*globalclassname; static SDL_Cursor*cursor[77]; static FILE*levelfp; static FILE*solutionfp; +static sqlite3_int64 leveluc,solutionuc; static FILE*hamarc_fp; static long hamarc_pos; +static sqlite3_stmt*readusercachest; static void hamarc_begin(FILE*fp,const char*name) { while(*name) fputc(*name++,fp); fwrite("\0\0\0\0",1,5,hamarc_fp=fp); hamarc_pos=ftell(fp); @@ -60,10 +67,171 @@ fputc(len>>24,hamarc_fp); fputc(len>>0,hamarc_fp); fputc(len>>8,hamarc_fp); fseek(hamarc_fp,end,SEEK_SET); } + +static sqlite3_int64 reset_usercache(FILE*fp,const char*nam,struct stat*stats,const char*suffix) { + sqlite3_stmt*st; + sqlite3_int64 t,id; + char buf[128]; + int i,z; + if(z=sqlite3_exec(userdb,"BEGIN;",0,0,0)) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + if(z=sqlite3_prepare_v2(userdb,"DELETE FROM `USERCACHEDATA` WHERE `FILE` = (SELECT `ID` FROM `USERCACHEINDEX` WHERE `NAME` = ?1);",-1,&st,0)) { + fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + } + sqlite3_bind_text(st,1,nam,-1,0); + while((z=sqlite3_step(st))==SQLITE_ROW); + if(z!=SQLITE_DONE) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + sqlite3_finalize(st); + if(z=sqlite3_prepare_v2(userdb,"DELETE FROM `USERCACHEINDEX` WHERE `NAME` = ?1;",-1,&st,0)) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + sqlite3_bind_text(st,1,nam,-1,0); + while((z=sqlite3_step(st))==SQLITE_ROW); + if(z!=SQLITE_DONE) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + sqlite3_finalize(st); + t=stats->st_mtime; + if(stats->st_ctime>t) t=stats->st_ctime; + if(z=sqlite3_prepare_v2(userdb,"INSERT INTO `USERCACHEINDEX`(`NAME`,`TIME`) VALUES(?1,?2);",-1,&st,0)) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + sqlite3_bind_text(st,1,nam,-1,0); + sqlite3_bind_int64(st,2,t); + while((z=sqlite3_step(st))==SQLITE_ROW); + if(z!=SQLITE_DONE) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + id=sqlite3_last_insert_rowid(userdb); + sqlite3_finalize(st); + if(z=sqlite3_prepare_v2(userdb,"INSERT INTO `USERCACHEDATA`(`FILE`,`LEVEL`,`NAME`,`OFFSET`) VALUES(?1,?2,?3,?4);",-1,&st,0)) { + fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + } + sqlite3_bind_int64(st,1,id); + rewind(fp); + for(;;) { + sqlite3_reset(st); + sqlite3_bind_null(st,3); + i=0; + for(;;) { + z=fgetc(fp); + if(z==EOF) goto done; + buf[i]=z; + if(!z) break; + ++i; + if(i==127) fatal("Found a long lump name; maybe this is not a real Hamster archive\n"); + } + t=fgetc(fp)<<16; t|=fgetc(fp)<<24; t|=fgetc(fp); t|=fgetc(fp)<<8; + if(feof(fp)) fatal("Invalid Hamster archive\n"); + sqlite3_bind_text(st,3,buf,i,0); + sqlite3_bind_int64(st,4,ftell(fp)); + if(i>4 && i<10 && !sqlite3_stricmp(buf+i-4,suffix)) { + for(z=0;z'9') goto nomatch; + if(*buf=='0' && i!=5) goto nomatch; + sqlite3_bind_int(st,2,strtol(buf,0,10)); + } else if(i==9 && !sqlite3_stricmp(buf,"CLASS.DEF")) { + sqlite3_bind_int(st,2,LUMP_CLASS_DEF); + } else if(i==9 && !sqlite3_stricmp(buf,"LEVEL.IDX")) { + sqlite3_bind_int(st,2,LUMP_LEVEL_IDX); + } else { + nomatch: sqlite3_bind_null(st,2); + } + } + done: + sqlite3_finalize(st); + if(z=sqlite3_exec(userdb,"COMMIT;",0,0,0)) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + return id; +} + +unsigned char*read_lump(int sol,int lvl,long*sz,sqlite3_value**us) { + // Returns a pointer to the data; must be freed using free(). + // If there is no data, returns null and sets *sz and *us to zero. + // Third argument is a pointer to a variable to store the data size (must be not null). + // Fourth argument may be null, and is user state (use sqlite3_value_free() to free it). + unsigned char*buf=0; + sqlite3_reset(readusercachest); + sqlite3_bind_int64(readusercachest,1,sol?solutionuc:leveluc); + sqlite3_bind_int(readusercachest,2,lvl); + if(sqlite3_step(readusercachest)==SQLITE_ROW) { + if(us) *us=sqlite3_value_dup(sqlite3_column_value(readusercachest,6)); + if(sqlite3_column_type(readusercachest,5)==SQLITE_BLOB) { + const unsigned char*con=sqlite3_column_blob(readusercachest,5); + *sz=sqlite3_column_bytes(readusercachest,5); + buf=malloc(*sz); + if(*sz && !buf) fatal("Allocation failed"); + memcpy(buf,con,*sz); + } else { + FILE*fp=sol?solutionfp:levelfp; + rewind(fp); + fseek(fp,sqlite3_column_int64(readusercachest,4)-4,SEEK_SET); + *sz=fgetc(fp)<<16; *sz|=fgetc(fp)<<24; *sz|=fgetc(fp); *sz|=fgetc(fp)<<8; + if(feof(fp) || *sz<0) fatal("Invalid Hamster archive\n"); + buf=malloc(*sz); + if(!buf) fatal("Allocation failed\n"); + if(!fread(buf,1,*sz,fp)) fatal("Unable to read data\n"); + rewind(fp); + } + } else { + *sz=0; + if(us) *us=0; + } + sqlite3_reset(readusercachest); + return buf; +} + +static void init_usercache(void) { + sqlite3_stmt*st; + int z; + sqlite3_int64 t1,t2; + char*nam1; + char*nam2; + char*nam3; + struct stat fst; + if(z=sqlite3_prepare_v2(userdb,"SELECT `ID`, `TIME` FROM `USERCACHEINDEX` WHERE `NAME` = ?1;",-1,&st,0)) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + nam1=sqlite3_mprintf("%s.level",basefilename); + if(!nam1) fatal("Allocation failed\n"); + nam2=realpath(nam1,0); + if(!nam2) fatal("Cannot find real path of '%s': %m\n",nam1); + levelfp=fopen(nam2,"r"); + if(!levelfp) fatal("Cannot open '%s' for reading: %m\n",nam2); + sqlite3_free(nam1); + sqlite3_bind_text(st,1,nam2,-1,0); + z=sqlite3_step(st); + if(z==SQLITE_ROW) { + leveluc=sqlite3_column_int64(st,0); + t1=sqlite3_column_int64(st,1); + } else if(z==SQLITE_DONE) { + leveluc=t1=-1; + } else { + fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + } + sqlite3_reset(st); + nam1=sqlite3_mprintf("%s.solution",basefilename); + if(!nam1) fatal("Allocation failed\n"); + nam3=realpath(nam1,0); + if(!nam3) fatal("Cannot find real path of '%s': %m\n",nam1); + if(!strcmp(nam2,nam3)) fatal("Level and solution files seem to be the same file\n"); + solutionfp=fopen(nam3,"r"); + if(!solutionfp) fatal("Cannot open '%s' for reading: %m\n",nam3); + sqlite3_free(nam1); + sqlite3_bind_text(st,1,nam3,-1,0); + z=sqlite3_step(st); + if(z==SQLITE_ROW) { + solutionuc=sqlite3_column_int64(st,0); + t2=sqlite3_column_int64(st,1); + } else if(z==SQLITE_DONE) { + solutionuc=t2=-1; + } else { + fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + } + sqlite3_finalize(st); + if(stat(nam2,&fst)) fatal("Unable to stat '%s': %m\n",nam2); + if(!fst.st_size) fatal("File '%s' has zero size\n",nam2); + if(fst.st_mtime>t1 || fst.st_ctime>t1) reset_usercache(levelfp,nam2,&fst,".LVL"); + if(stat(nam3,&fst)) fatal("Unable to stat '%s': %m\n",nam3); + if(!fst.st_size) fatal("File '%s' has zero size\n",nam2); + if(fst.st_mtime>t1 || fst.st_ctime>t1) reset_usercache(levelfp,nam2,&fst,".LVL"); + free(nam2); + free(nam3); + if(z=sqlite3_prepare_v3(userdb,"SELECT * FROM `USERCACHEDATA` WHERE `FILE` = ?1 AND `LEVEL` = ?2;",-1,SQLITE_PREPARE_PERSISTENT,&readusercachest,0)) { + fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); + } +} static void init_sql(void) { char*s; char*p; const char*v; Index: mbtofhm.c ================================================================== --- mbtofhm.c +++ mbtofhm.c @@ -746,11 +746,11 @@ st=0; break; case 133: fprintf(fp," DelInventory"); st=0; break; case 134: - fprintf(fp," (for %%%s)",vars+*op*8); + fprintf(fp," for =%%%s",vars+*op*8); ind+=2; len+=2; st=0; break; case 135: fseek(fp,-1,SEEK_CUR); fprintf(fp,"next"); ind-=2; len+=6; st=0; break;