#if 0 gcc $CFLAGS -c -Wno-unused-result lumped.c `sdl-config --cflags` exit #endif #include "common.h" #include #include #include #include typedef struct { char name[16]; // name in uppercase (null-terminated) Uint32 offset; // offset of beginning of data; if zero, then it is a new lump Uint32 length; Uint8*data; // if changed or in use Uint32 refcount; Uint8 flag; // bit0=changed bit1=writing } Lump; char*world_name; Uint32 lump_size; static FILE*worldfile; static Lump*lumps; static int nlumps; typedef struct { char name[16]; Uint32 length; Uint32 wlength; Uint8*data; Uint32 pos; Uint32 id; Uint8 writing; } LumpCookie; static void convert_lump_name(const char*in,char*out) { int i; for(i=0;i<15;i++) { out[i]=in[i]; if(!in[i]) break; if(in[i]>='a' && in[i]<='z') out[i]+='A'-'a'; } out[15]=0; } static int compare_lump_name(const void*a,const void*b) { const Lump*x=a; const Lump*y=b; return strcmp(x->name,y->name); } static ssize_t lump_read(void*cookie,char*buf,size_t size) { LumpCookie*co=cookie; if(co->pos>=co->length || !size) return 0; if(size>co->length-co->pos) size=co->length-co->pos; memcpy(buf,co->data+co->pos,size); co->pos+=size; return size; } static ssize_t lump_write(void*cookie,const char*buf,size_t size) { LumpCookie*co=cookie; if(!size) return 0; if(co->pos+size>co->length) co->length=co->pos+size; if(co->pos+size>co->wlength) { if(co->wlength>=1024) co->wlength+=co->wlength>>1; else co->wlength=1024; if(co->pos+size>co->wlength) co->wlength=co->pos+size; co->data=realloc(co->data,co->wlength); if(!co->data) err(1,"Allocation failed"); } memcpy(co->data+co->pos,buf,size); co->pos+=size; return size; } static int lump_seek(void*cookie,off64_t*offset,int whence) { LumpCookie*co=cookie; switch(whence) { case SEEK_SET: co->pos=*offset; break; case SEEK_CUR: co->pos+=*offset; break; case SEEK_END: co->pos=co->length+*offset; break; default: return -1; } *offset=co->pos; return 0; } static int lump_close(void*cookie) { Lump*obj; LumpCookie*co=cookie; Lump key; if(co->idid].name,co->name)) { obj=lumps+co->id; } else { memcpy(key.name,co->name,16); obj=bsearch(&key,lumps,nlumps,sizeof(Lump),compare_lump_name); if(!obj) errx(1,"Unexpected error in lump_close"); } --obj->refcount; if(co->writing) { obj->flag&=~2; obj->data=co->data; obj->length=co->length; } if(!obj->refcount && !obj->flag) { free(obj->data); obj->data=0; } free(co); return 0; } FILE*open_lump(const char*name,const char*mode) { static const cookie_io_functions_t cf={ .read=lump_read, .write=lump_write, .seek=lump_seek, .close=lump_close, }; LumpCookie*co; FILE*fp; Lump key; Lump*obj; lump_size=0; convert_lump_name(name,key.name); if(strchr(mode,'x')) { if(obj=bsearch(&key,lumps,nlumps,sizeof(Lump),compare_lump_name)) { if(!obj->length) goto found; return 0; } goto notfound; } try_again: obj=bsearch(&key,lumps,nlumps,sizeof(Lump),compare_lump_name); if(!obj) { notfound: if(*mode=='r') { if(!mode[1] && config.extra_lump_name && !strcmp(config.extra_lump_name,key.name)) return fopen(config.extra_lump_file,"r"); return 0; } lumps=realloc(lumps,(nlumps+1)*sizeof(Lump)); if(!lumps) err(1,"Allocation failed"); lumps[nlumps]=key; lumps[nlumps].offset=lumps[nlumps].length=lumps[nlumps].refcount=0; lumps[nlumps].data=0; lumps[nlumps].flag=1; ++nlumps; qsort(lumps,nlumps,sizeof(Lump),compare_lump_name); goto try_again; } found: if(obj->flag&2) return 0; if(obj->refcount && (*mode!='r' || strchr(mode,'+'))) return 0; ++obj->refcount; co=malloc(sizeof(LumpCookie)); if(!co) err(1,"Allocation failed"); memcpy(co->name,obj->name,16); if(*mode=='w') { obj->length=0; free(obj->data); obj->data=0; } lump_size=co->length=obj->length; co->data=obj->data; co->pos=co->wlength=0; co->id=obj-lumps; co->writing=0; if(obj->length && !obj->data) { fseek(worldfile,obj->offset,SEEK_SET); co->data=obj->data=malloc(obj->length); if(!co->data) err(1,"Allocation failed"); fread(co->data,1,obj->length,worldfile); } if(*mode!='r' || strchr(mode,'+')) co->writing=obj->flag|=3; fp=fopencookie(co,mode,cf); if(!fp) errx(1,"Unable to open lump cookie stream"); return fp; } FILE*open_lump_by_number(Uint16 id,const char*ext,const char*mode) { static char name[16]; snprintf(name,15,"%04X.%s",id,ext); return open_lump(name,mode); } void revert_lump(const char*name) { Lump key; Lump*obj; convert_lump_name(name,key.name); if((obj=bsearch(&key,lumps,nlumps,sizeof(Lump),compare_lump_name)) && obj->flag&1) { if((obj->flag&2) || obj->refcount) errx(1,"Trying to revert a lump which is in use"); free(obj->data); obj->data=0; obj->flag=0; if(obj->offset && worldfile) { fseek(worldfile,obj->offset-4,SEEK_SET); obj->length=fgetc(worldfile)<<16; obj->length|=fgetc(worldfile)<<24; obj->length|=fgetc(worldfile)<<0; obj->length|=fgetc(worldfile)<<8; } else { obj->length=0; } } } void revert_lump_by_number(Uint16 id,const char*ext) { static char name[16]; snprintf(name,15,"%04X.%s",id,ext); revert_lump(name); } int open_world(const char*name) { FILE*fp=fopen(name,"r"); Uint32 n; int c,i; if(!fp) { warn("Cannot open file '%s'",name); return -1; } if(flock(fileno(fp),LOCK_SH|LOCK_NB)) { warn("Cannot lock file '%s'",name); fclose(fp); return -1; } if(name!=world_name) { free(world_name); world_name=strdup(name); if(!world_name) err(1,"Allocation failed"); } if(worldfile) fclose(worldfile); worldfile=fp; for(n=0;n0); if(c<0) break; ++nlumps; n=fgetc(fp)<<16; n|=fgetc(fp)<<24; n|=fgetc(fp); n|=fgetc(fp)<<8; fseek(fp,n,SEEK_CUR); } rewind(fp); if(!nlumps) return 0; lumps=calloc(nlumps,sizeof(Lump)); if(!lumps) err(1,"Allocation failed"); for(n=0;n0) { if(i<16) lumps[n].name[i++]=c+(c>='a' && c<='z'?'A'-'a':0); else lumps[n].name[0]=0; } lumps[n].length=fgetc(fp)<<16; lumps[n].length|=fgetc(fp)<<24; lumps[n].length|=fgetc(fp); lumps[n].length|=fgetc(fp)<<8; lumps[n].offset=ftell(fp); fseek(fp,lumps[n].length,SEEK_CUR); if(!lumps[n].name[0]) --n,--nlumps; } qsort(lumps,nlumps,sizeof(Lump),compare_lump_name); return 0; } void close_world(void) { Uint32 n; free(world_name); world_name=0; if(worldfile) fclose(worldfile); worldfile=0; for(n=0;n>16,fp); fputc(o>>24,fp); fputc(o>>0,fp); fputc(o>>8,fp); o=lumps[n].offset; if(!name) lumps[n].offset=ftell(fp); if(lumps[n].data) { fwrite(lumps[n].data,1,lumps[n].length,fp); } else { fseek(worldfile,o,SEEK_SET); o=lumps[n].length; while(o>1024) { fread(buf,1,1024,worldfile); fwrite(buf,1,1024,fp); o-=1024; } if(o) { fread(buf,1,o,worldfile); fwrite(buf,1,o,fp); } } } n=ferror(fp); fclose(fp); if(n) { if(!name) err(1,"Error writing to new world file"); warn("Error writing to new world file"); return -1; } if(!name) { if(rename(config.temporary_file_1,world_name)) err(1,"Cannot replace world file (the new file is now called %s)",config.temporary_file_1); flock(fileno(worldfile),LOCK_UN); fclose(worldfile); worldfile=fopen(world_name,"r"); if(!worldfile) err(1,"Cannot reopen world file '%s'",name); if(flock(fileno(worldfile),LOCK_SH|LOCK_NB)) { warn("Cannot lock file '%s'",world_name); fclose(worldfile); worldfile=0; return -1; } } return 0; } int save_game(FILE*fp) { FILE*f; Uint32 n,o; for(n=0;n>16,fp); fputc(o>>24,fp); fputc(o>>0,fp); fputc(o>>8,fp); if(o) fwrite(lumps[n].data,1,o,fp); } return 0; } int restore_game(FILE*fp) { Uint8 buf[1024]; Uint32 n,o; FILE*f; int c; for(n=0;n0) if(o<16) buf[o++]=c; if(c<0) break; buf[o]=0; o=fgetc(fp)<<16; o|=fgetc(fp)<<24; o|=fgetc(fp); o|=fgetc(fp)<<8; f=open_lump(buf,"w"); if(!f) errx(1,"Error opening lump for writing"); while(o>1024) { fread(buf,1,1024,fp); fwrite(buf,1,1024,f); o-=1024; } if(o) { fread(buf,1,o,fp); fwrite(buf,1,o,f); } fclose(f); } return 0; } size_t copy_stream(FILE*in,FILE*out,size_t len) { char buf[0x2000]; size_t t=0; size_t n; size_t s; while(len) { if(len>0x2000) n=0x2000; else n=len; if(s=fread(buf,1,n,in)) fwrite(buf,1,s,out); if(s