#ifndef SPRITEFORGE_SFA_H #define SPRITEFORGE_SFA_H #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define SFA_VERSION_MAJOR 1u #define SFA_VERSION_MINOR 0u #define SFA_FLAG_NORMALS 1u #define SFA_FLAG_DEPTH 2u #define SFA_FLAG_PALETTE_ALPHA 0x80000000u typedef enum sfa_result { SFA_OK=0, SFA_BAD_ARGUMENT, SFA_TRUNCATED, SFA_BAD_MAGIC, SFA_UNSUPPORTED_VERSION, SFA_UNSUPPORTED_FLAGS, SFA_CORRUPT, SFA_NO_MEMORY, SFA_IO_ERROR } sfa_result; typedef enum sfa_blend_mode { SFA_BLEND_OPAQUE=0, SFA_BLEND_ALPHA25, SFA_BLEND_ALPHA50, SFA_BLEND_ALPHA75, SFA_BLEND_ADDITIVE, SFA_BLEND_SUBTRACTIVE } sfa_blend_mode; typedef struct sfa_view { const uint8_t* data; size_t size; uint32_t flags, frame_count; uint16_t animation_count, direction_count, layer_count; uint32_t strings_offset,animations_offset,directions_offset,frames_offset,layer_order_offset; } sfa_view; typedef struct sfa_animation { uint32_t name_hash,first_frame;uint16_t frames_per_direction,direction_count;const char* name; } sfa_animation; typedef struct sfa_direction { int16_t centidegrees;uint32_t first_frame; } sfa_direction; typedef struct sfa_frame { uint16_t width,height; int16_t pivot_x,pivot_y; uint16_t duration_ms,flags; float depth_min,depth_max; uint32_t offset[3],size[3]; } sfa_frame; typedef struct sfa_owned { uint8_t* data; size_t size; sfa_view view; } sfa_owned; typedef struct sfa_surface { uint32_t* pixels; int width,height,stride; } sfa_surface; typedef struct sfa_light { float x,y,z; uint8_t ambient; } sfa_light; static uint16_t sfa_u16(const uint8_t* p) { return (uint16_t)(p[0]|((uint16_t)p[1]<<8)); } static int16_t sfa_i16(const uint8_t* p) { return (int16_t)sfa_u16(p); } static uint32_t sfa_u32(const uint8_t* p) { return (uint32_t)p[0]|((uint32_t)p[1]<<8)|((uint32_t)p[2]<<16)|((uint32_t)p[3]<<24); } static float sfa_f32(const uint8_t* p) { uint32_t u=sfa_u32(p); float f; memcpy(&f,&u,4); return f; } static int sfa_range(size_t n, uint32_t off, size_t amount) { return off<=n && amount<=n-off; } static sfa_result sfa_open(const void* bytes, size_t size, sfa_view* out) { const uint8_t* p=(const uint8_t*)bytes; uint32_t file_size, known=3u; if(!p||!out) return SFA_BAD_ARGUMENT; if(size<64) return SFA_TRUNCATED; if(memcmp(p,"SFA\0",4)) return SFA_BAD_MAGIC; if(sfa_u16(p+4)!=1 || sfa_u16(p+6)>0) return SFA_UNSUPPORTED_VERSION; out->flags=sfa_u32(p+8); if((out->flags & ~known)!=0) return SFA_UNSUPPORTED_FLAGS; file_size=sfa_u32(p+12); if(file_size!=size) return SFA_CORRUPT; out->animation_count=sfa_u16(p+16); out->direction_count=sfa_u16(p+18); out->frame_count=sfa_u32(p+20); out->layer_count=sfa_u16(p+24); out->strings_offset=sfa_u32(p+28);out->animations_offset=sfa_u32(p+32);out->directions_offset=sfa_u32(p+36); out->frames_offset=sfa_u32(p+40); out->layer_order_offset=sfa_u32(p+44); if(!sfa_range(size,out->animations_offset,(size_t)out->animation_count*16u))return SFA_CORRUPT; if(!sfa_range(size,out->directions_offset,(size_t)out->direction_count*8u))return SFA_CORRUPT; if(!sfa_range(size,out->frames_offset,(size_t)out->frame_count*48u)) return SFA_CORRUPT; if(!sfa_range(size,out->layer_order_offset,(size_t)out->frame_count*out->layer_count*2u)) return SFA_CORRUPT; out->data=p; out->size=size; return SFA_OK; } static sfa_result sfa_get_animation(const sfa_view* v,uint16_t index,sfa_animation* out){ const uint8_t* p;uint32_t name_offset,absolute;const uint8_t* end; if(!v||!out||index>=v->animation_count)return SFA_BAD_ARGUMENT;p=v->data+v->animations_offset+(size_t)index*16u; out->name_hash=sfa_u32(p);name_offset=sfa_u32(p+4);out->first_frame=sfa_u32(p+8);out->frames_per_direction=sfa_u16(p+12);out->direction_count=sfa_u16(p+14); absolute=v->strings_offset+name_offset;if(absolutestrings_offset||absolute>=v->animations_offset)return SFA_CORRUPT; end=(const uint8_t*)memchr(v->data+absolute,0,v->animations_offset-absolute);if(!end)return SFA_CORRUPT;out->name=(const char*)(v->data+absolute);return SFA_OK; } static sfa_result sfa_get_direction(const sfa_view* v,uint16_t index,sfa_direction* out){ const uint8_t* p;if(!v||!out||index>=v->direction_count)return SFA_BAD_ARGUMENT;p=v->data+v->directions_offset+(size_t)index*8u; out->centidegrees=sfa_i16(p);if(sfa_u16(p+2)!=0)return SFA_CORRUPT;out->first_frame=sfa_u32(p+4);return SFA_OK; } static sfa_result sfa_get_layer_order(const sfa_view* v,uint32_t frame_index,uint16_t position,uint16_t* layer){ size_t index;if(!v||!layer||frame_index>=v->frame_count||position>=v->layer_count)return SFA_BAD_ARGUMENT; index=(size_t)frame_index*v->layer_count+position;*layer=sfa_u16(v->data+v->layer_order_offset+index*2u);if(*layer>=v->layer_count)return SFA_CORRUPT;return SFA_OK; } static sfa_result sfa_get_frame(const sfa_view* v, uint32_t index, sfa_frame* f) { const uint8_t* p; int i; if(!v||!f||index>=v->frame_count) return SFA_BAD_ARGUMENT; p=v->data+v->frames_offset+(size_t)index*48u; f->width=sfa_u16(p); f->height=sfa_u16(p+2); f->pivot_x=sfa_i16(p+4); f->pivot_y=sfa_i16(p+6); f->duration_ms=sfa_u16(p+8); f->flags=sfa_u16(p+10); f->depth_min=sfa_f32(p+12); f->depth_max=sfa_f32(p+16); for(i=0;i<3;i++){ f->offset[i]=sfa_u32(p+20+i*8); f->size[i]=sfa_u32(p+24+i*8); if(f->offset[i] && !sfa_range(v->size,f->offset[i],f->size[i])) return SFA_CORRUPT; } return SFA_OK; } static uint32_t sfa_mix(uint32_t d,uint32_t s,sfa_blend_mode m){ unsigned dr=d&255,dg=(d>>8)&255,db=(d>>16)&255,sr=s&255,sg=(s>>8)&255,sb=(s>>16)&255,a; if(m==SFA_BLEND_OPAQUE)return s|0xff000000u; if(m==SFA_BLEND_ADDITIVE){sr=dr+sr>255?255:dr+sr;sg=dg+sg>255?255:dg+sg;sb=db+sb>255?255:db+sb;} else if(m==SFA_BLEND_SUBTRACTIVE){sr=dr>sr?dr-sr:0;sg=dg>sg?dg-sg:0;sb=db>sb?db-sb:0;} else {a=m==SFA_BLEND_ALPHA25?64u:m==SFA_BLEND_ALPHA50?128u:192u;sr=(dr*(256-a)+sr*a)>>8;sg=(dg*(256-a)+sg*a)>>8;sb=(db*(256-a)+sb*a)>>8;} return 0xff000000u|sr|(sg<<8)|(sb<<16); } static sfa_result sfa_blit(const sfa_view* v,const sfa_frame* f,const uint8_t rgb[768],const uint8_t* map, sfa_surface dst,int anchor_x,int anchor_y,sfa_blend_mode mode){ int sy,dy,x0=anchor_x-f->pivot_x,y0=anchor_y-f->pivot_y; const uint8_t* base; uint32_t table; if(!v||!f||!rgb||!dst.pixels||dst.strideoffset[0])return SFA_BAD_ARGUMENT; base=v->data+f->offset[0]; table=4u*((uint32_t)f->height+1u); if(f->size[0]height;sy++){const uint8_t *p,*end;uint16_t n,i;dy=y0+sy;if(dy<0||dy>=dst.height)continue; {uint32_t a=sfa_u32(base+sy*4),b=sfa_u32(base+(sy+1)*4);if(a>b||b>f->size[0]||b-a<2)return SFA_CORRUPT;p=base+a;end=base+b;} n=sfa_u16(p);p+=2;for(i=0;i=0&&dx0)return SFA_UNSUPPORTED_VERSION; if(sfa_u32(p+8)!=0)return SFA_UNSUPPORTED_FLAGS; file_size=sfa_u32(p+12);if(file_size!=size)return SFA_CORRUPT; out->palette_count=sfa_u32(p+16);out->colormap_count=sfa_u32(p+20);out->records_offset=sfa_u32(p+28);out->colormap_records_offset=sfa_u32(p+32); if(!sfa_range(size,out->records_offset,(size_t)out->palette_count*20u)||!sfa_range(size,out->colormap_records_offset,(size_t)out->colormap_count*20u))return SFA_CORRUPT; out->data=p;out->size=size;return SFA_OK; } static sfa_result sfp_palette(const sfp_view* v,uint32_t index,const uint8_t** rgb){ const uint8_t* r;uint32_t off;if(!v||!rgb||index>=v->palette_count)return SFA_BAD_ARGUMENT; r=v->data+v->records_offset+(size_t)index*20u;off=sfa_u32(r+8); if(!sfa_range(v->size,off,768))return SFA_CORRUPT;*rgb=v->data+off;return SFA_OK; } static sfa_result sfp_colormap(const sfp_view* v,uint32_t index,const uint8_t** map){ const uint8_t* r;uint32_t off;if(!v||!map||index>=v->colormap_count)return SFA_BAD_ARGUMENT; r=v->data+v->colormap_records_offset+(size_t)index*20u;off=sfa_u32(r+8); if(!sfa_range(v->size,off,256))return SFA_CORRUPT;*map=v->data+off;return SFA_OK; } static sfa_result sfa_blit_lit(const sfa_view* v,const sfa_frame* f,const uint8_t rgb[768],const uint8_t* map, sfa_surface dst,int ax,int ay,sfa_blend_mode mode,sfa_light light){ int sy,dy,x0=ax-f->pivot_x,y0=ay-f->pivot_y;const uint8_t *cb,*nb;float ll; if(!v||!f||!rgb||!dst.pixels||dst.strideoffset[0]||!f->offset[1])return SFA_BAD_ARGUMENT; cb=v->data+f->offset[0];nb=v->data+f->offset[1]; if(f->size[0]<4u*((uint32_t)f->height+1u)||f->size[1]<4u*((uint32_t)f->height+1u))return SFA_CORRUPT; ll=sqrtf(light.x*light.x+light.y*light.y+light.z*light.z);if(ll>0){light.x/=ll;light.y/=ll;light.z/=ll;} for(sy=0;sy<(int)f->height;sy++){const uint8_t *cp,*ce,*np,*ne;uint32_t ca,cz,na,nz;uint16_t count,ncount,i; dy=y0+sy;if(dy<0||dy>=dst.height)continue; ca=sfa_u32(cb+sy*4);cz=sfa_u32(cb+(sy+1)*4);na=sfa_u32(nb+sy*4);nz=sfa_u32(nb+(sy+1)*4); if(ca>cz||cz>f->size[0]||na>nz||nz>f->size[1]||cz-ca<2||nz-na<2)return SFA_CORRUPT; cp=cb+ca;ce=cb+cz;np=nb+na;ne=nb+nz;count=sfa_u16(cp);ncount=sfa_u16(np);cp+=2;np+=2;if(count!=ncount)return SFA_CORRUPT; for(i=0;i=0&&dx0?d*(255-light.ambient):0);unsigned r=rgb[k*3]*q/255,g=rgb[k*3+1]*q/255,b=rgb[k*3+2]*q/255;uint32_t c=r|(g<<8)|(b<<16);uint32_t* target=dst.pixels+(size_t)dy*dst.stride+dx;*target=sfa_mix(*target,c,mode);}} cp+=n;np+=(size_t)n*2;} }return SFA_OK; } static sfa_result sfa_load_file(const char* path,sfa_owned* o){FILE* f;long n;size_t got;sfa_result r;if(!path||!o)return SFA_BAD_ARGUMENT;memset(o,0,sizeof(*o));f=fopen(path,"rb");if(!f)return SFA_IO_ERROR; if(fseek(f,0,SEEK_END)|| (n=ftell(f))<0 || fseek(f,0,SEEK_SET)){fclose(f);return SFA_IO_ERROR;}o->data=(uint8_t*)malloc((size_t)n);if(!o->data){fclose(f);return SFA_NO_MEMORY;}got=fread(o->data,1,(size_t)n,f);fclose(f);if(got!=(size_t)n){free(o->data);memset(o,0,sizeof(*o));return SFA_IO_ERROR;}o->size=(size_t)n;r=sfa_open(o->data,o->size,&o->view);if(r!=SFA_OK){free(o->data);memset(o,0,sizeof(*o));}return r;} static void sfa_free_file(sfa_owned* o){if(o){free(o->data);memset(o,0,sizeof(*o));}} #ifdef __cplusplus } #endif #endif