2 #include "SpriteFile.h"
8 #include "endianness.h"
9 #include "BitmapFuncs.h"
14 #define ADS ":::AGSprite " VERSION " by rofl0r:::"
17 #define breakpoint() __asm__("int3")
19 #define breakpoint() do{}while(0)
22 #define FL_EXTRACT 1<<0
24 #define FL_VERBOSE 1<<2
25 #define FL_UNCOMPRESSED 1<<3
26 #define FL_HICOLOR 1<<4
27 #define FL_HICOLOR_SIMPLE (1<<5)
28 #define FL_SPRINDEX (1<<6)
30 static int debug_pic
= -1, flags
, filenr
;
32 static int lookup_palette(unsigned color
, unsigned *palette
, int ncols
)
35 for(i
=0; i
<ncols
; ++i
)
36 if(palette
[i
] == color
) return i
;
40 static void rgb565_to_888(unsigned lo
, unsigned hi
, unsigned *r
, unsigned *g
, unsigned *b
)
42 *r
= (hi
& ~7) | (hi
>> 5);
43 *g
= ((hi
& 7) << 5) | ((lo
& 224) >> 3) | ((hi
& 7) >> 1);
44 *b
= ((lo
& 31) << 3) | ((lo
& 28) >> 2);
47 static int convert_16_to_24(ImageData
*d
) {
48 size_t outsz
= d
->width
*d
->height
*3;
49 unsigned char *out
= malloc(outsz
),
50 *p
= out
, *pe
= out
+ outsz
,
57 rgb565_to_888(lo
, hi
, &r
, &g
, &b
);
69 static int create_palette_pic(const ImageData
* d
, unsigned *palette
, unsigned char **data
)
72 unsigned char *p
= d
->data
, *q
= p
+ d
->data_size
;
73 *data
= malloc(d
->width
* d
->height
);
74 unsigned char *o
= *data
, *e
= o
+ (d
->width
* d
->height
);
76 while(p
< q
&& o
< e
) {
77 unsigned col
, r
, g
, b
;
78 switch(d
->bytesperpixel
) {
82 rgb565_to_888(lo
, hi
, &r
, &g
, &b
);
89 if(d
->bytesperpixel
== 4) a
= *(p
++);
93 col
= a
<< 24 | r
<< 16 | g
<< 8 | b
;
94 int n
= lookup_palette(col
, palette
, ret
);
102 palette
[ret
++] = col
;
109 static void write_tga(char *name
, ImageData
* d
, unsigned char *palette
)
112 unsigned char *paldata
= 0, *data
= d
->data
;
113 unsigned bpp
= d
->bytesperpixel
;
114 unsigned data_size
= d
->data_size
;
118 FILE *f
= fopen(name
, "w");
120 fprintf(stderr
, "error opening %s\n", name
);
125 if(!palette
) for(i
=0; i
<256; ++i
) pal
[i
] = rand();
126 else for(i
=0; i
<256; ++i
) {
127 unsigned r
= *(palette
++);
128 unsigned g
= *(palette
++);
129 unsigned b
= *(palette
++);
130 pal
[i
] = r
<< 16 | g
<< 8 | b
;
132 } else if( /* bpp != 2 && */
133 (palcount
= create_palette_pic(d
, pal
, &paldata
)) > 0) {
134 /* can be saved as 8 bit palettized image */
137 data_size
= d
->width
*d
->height
;
138 } else if(bpp
== 2 && convert_16_to_24(d
)) {
141 data_size
= d
->data_size
;
143 unsigned char *rle_data
= 0;
144 unsigned rle_data_size
= rle_encode(data
, data_size
, bpp
, &rle_data
);
146 if(rle_data
&& rle_data_size
< data_size
) {
147 data_size
= rle_data_size
;
151 struct TargaHeader hdr
= {
153 .colourmaptype
= bpp
== 1 ? 1 : 0,
154 .datatypecode
= bpp
== 1 ?
155 (use_rle
? TIT_RLE_COLOR_MAPPED
: TIT_COLOR_MAPPED
) :
156 (use_rle
? TIT_RLE_TRUE_COLOR
: TIT_TRUE_COLOR
),
157 .colourmaporigin
= 0,
158 .colourmaplength
= bpp
== 1 ? le16(palcount
) : 0,
159 .colourmapdepth
= bpp
== 1 ? 24 : 0,
161 .y_origin
= le16(d
->height
), /* image starts at the top */
162 .width
= le16(d
->width
),
163 .height
= le16(d
->height
),
164 .bitsperpixel
= bpp
*8,
165 .imagedescriptor
= 0x20, /* image starts at the top */
167 fwrite(&hdr
, 1, sizeof hdr
, f
);
169 if(bpp
== 1) for(i
=0; i
<palcount
; ++i
) {
171 fwrite(&tmp
, 1, 3, f
);
173 fwrite(data
, 1, data_size
, f
);
179 static int extract(char* file
, char* dir
) {
180 if(access(dir
, R_OK
) == -1 && errno
== ENOENT
) {
181 mkdir(dir
, S_IRWXU
| S_IRWXG
| S_IROTH
| S_IXOTH
);
186 ret
= AF_open(&f
, file
);
188 fprintf(stderr
, "error opening %s\n", file
);
191 if(flags
& FL_VERBOSE
) printf("processing spritefile TOC...\n");
192 ret
= SpriteFile_read(&f
, &sf
);
194 fprintf(stderr
, "error reading spritefile %s\n", file
);
201 snprintf(buf
, sizeof buf
, "%s/agsprite.pal", dir
);
202 FILE *pal
= fopen(buf
, "w");
204 fwrite(sf
.palette
, 1, 256*3, pal
);
207 snprintf(buf
, sizeof buf
, "%s/agsprite.info", dir
);
208 info
= fopen(buf
, "w");
211 fprintf(stderr
, "error opening %s\n", buf
);
215 "info=infofile created by " ADS
"\n"
216 "info=this file is needed to reconstruct acsprset.spr\n"
217 "spritecacheversion=%d\n"
221 , sf
.version
, sf
.num_sprites
, sf
.id
, sf
.palette
? "agsprite.pal" : ""
225 for(i
=0; i
<sf
.num_sprites
; i
++) {
226 if(debug_pic
== i
) breakpoint();
228 if(SpriteFile_extract(&f
, &sf
, i
, &d
)) {
230 snprintf(namebuf
, sizeof namebuf
, "sprite%06d_%02d_%dx%d.tga", i
, d
.bytesperpixel
*8, d
.width
, d
.height
);
231 fprintf(info
, "%d=%s\n", i
, namebuf
);
232 if(flags
& FL_VERBOSE
) printf("extracting sprite %d (%s)\n", i
, namebuf
);
234 snprintf(filename
, sizeof filename
, "%s/%s", dir
, namebuf
);
235 write_tga(filename
, &d
, sf
.palette
);
243 static int read_tga(FILE *f
, ImageData
*idata
, int skip_palette
) {
244 struct TargaHeader hdr
;
245 struct TargaFooter ftr
;
246 fread(&hdr
, 1, sizeof hdr
, f
);
247 fseek(f
, 0, SEEK_END
);
248 off_t fs
= ftello(f
);
249 if(fs
> sizeof ftr
) {
250 fseek(f
, 0-sizeof ftr
, SEEK_END
);
251 fread(&ftr
, 1, sizeof ftr
, f
);
252 if(!memcmp(ftr
.signature
, TARGA_FOOTER_SIGNATURE
, sizeof ftr
.signature
))
255 hdr
.colourmaplength
= le16(hdr
.colourmaplength
);
256 hdr
.colourmapdepth
= le16(hdr
.colourmapdepth
);
257 hdr
.x_origin
= le16(hdr
.x_origin
);
258 hdr
.y_origin
= le16(hdr
.y_origin
);
259 hdr
.width
= le16(hdr
.width
);
260 hdr
.height
= le16(hdr
.height
);
262 fseek(f
, sizeof hdr
+ hdr
.idlength
, SEEK_SET
);
264 unsigned char *data
= malloc(fs
), *palette
= 0;
266 fread(data
, 1, fs
, f
);
267 if(hdr
.colourmaptype
) {
269 palsz
= hdr
.colourmaplength
* hdr
.colourmapdepth
/8;
270 if(fs
<= palsz
) return 0;
274 unsigned char *workdata
= 0;
276 switch(hdr
.datatypecode
) {
277 case TIT_RLE_COLOR_MAPPED
:
278 case TIT_RLE_TRUE_COLOR
:
279 tmp
= hdr
.width
*hdr
.height
*hdr
.bitsperpixel
/8;
280 workdata
= malloc(tmp
);
281 rle_decode(data
, fs
, hdr
.bitsperpixel
/8, workdata
, tmp
);
283 case TIT_COLOR_MAPPED
:
290 idata
->width
= hdr
.width
;
291 idata
->height
= hdr
.height
;
293 idata
->bytesperpixel
= hdr
.bitsperpixel
/8;
295 idata
->bytesperpixel
= hdr
.colourmapdepth
? hdr
.colourmapdepth
/8 : hdr
.bitsperpixel
/8;
297 tmp
= idata
->width
*idata
->height
*idata
->bytesperpixel
;
298 idata
->data_size
= tmp
;
299 idata
->data
= malloc(tmp
);
300 if(palette
&& !skip_palette
) {
301 unsigned i
, j
, bpp
= hdr
.colourmapdepth
/8;
302 unsigned char *p
= idata
->data
, *q
= workdata
;
303 for(i
=0; i
< idata
->width
*idata
->height
; ++i
) {
304 unsigned idx
= *(q
++);
305 if(idx
>= hdr
.colourmaplength
) return 0;
306 for(j
=0; j
< bpp
; ++j
)
307 *(p
++) = palette
[idx
*bpp
+j
];
310 memcpy(idata
->data
, workdata
, tmp
);
312 if(workdata
!= data
) free(workdata
);
313 if(palette
) free(palette
);
318 static int is_upscaled_16bit(ImageData
*d
) {
320 for(i
=0; i
<d
->data_size
; i
++) {
321 if(i
%3 != 1) { /* topmost 3 bits appended */
322 if((d
->data
[i
] & 7) != (d
->data
[i
] >> 5))
324 } else { /* topmost 2 bits appended */
325 if((d
->data
[i
] & 3) != (d
->data
[i
] >> 6))
332 static int rawx_to_ags16(ImageData
*d
, int bpp
) {
333 int i
, imax
= d
->data_size
/bpp
;
334 unsigned char *data
= malloc(d
->width
*d
->height
*2), *p
= data
;
336 for(i
=0; i
<imax
; i
++) {
337 unsigned b
= d
->data
[i
*bpp
+0];
338 unsigned g
= d
->data
[i
*bpp
+1];
339 unsigned r
= d
->data
[i
*bpp
+2];
340 unsigned hi
= (r
& ~7) | (g
>> 5);
341 unsigned lo
= ((g
& 28) << 3) | (b
>> 3);
347 d
->bytesperpixel
= 2;
348 d
->data_size
= d
->width
*d
->height
*2;
352 static int raw24_to_ags16(ImageData
*d
) {
353 return rawx_to_ags16(d
, 3);
355 static int raw32_to_ags16(ImageData
*d
) {
356 return rawx_to_ags16(d
, 4);
359 static int raw24_to_32(ImageData
*d
) {
360 unsigned char* data
= malloc(d
->width
*d
->height
*4), *p
= data
, *q
= d
->data
;
363 for(i
=0;i
<d
->width
*d
->height
;++i
) {
370 /* restore transparency for "magic magenta" pixels */
371 *(p
++) = "\xff\0"[!!(r
== 0xff && g
== 0 && b
== 0xff)];
375 d
->bytesperpixel
= 4;
376 d
->data_size
= d
->width
*d
->height
*4;
379 static int raw32_swap_alpha(ImageData
*d
) {
381 unsigned char *p
= d
->data
, *pe
= d
->data
+d
->data_size
;
383 unsigned char *q
= p
;
396 /* return true if alpha uses only values 0 (fully transparent)
397 or 0xff (not transparent), and transparency is only used
398 for "magic magenta". */
399 static int is_hicolor_candidate(ImageData
*d
) {
400 unsigned char *p
= d
->data
, *pe
= d
->data
+d
->data_size
;
409 if(!(r
== 0xff && g
== 0 && b
== 0xff))
417 static int tga_to_ags(ImageData
*d
, int org_bpp
) {
418 /* convert raw image data to something acceptable for ags */
419 switch(d
->bytesperpixel
) {
421 if(flags
& FL_HICOLOR
) return raw32_to_ags16(d
);
422 else if(flags
& FL_HICOLOR_SIMPLE
&& is_hicolor_candidate(d
)) {
423 if(flags
& FL_VERBOSE
) printf("converting %d to 16bpp\n", filenr
);
424 return raw32_to_ags16(d
);
425 } else return raw32_swap_alpha(d
);
427 if(flags
& FL_HICOLOR
) return raw24_to_ags16(d
);
428 if(org_bpp
== 2 && is_upscaled_16bit(d
)) return raw24_to_ags16(d
);
429 else return raw24_to_32(d
);
434 static int pack(char* file
, char* dir
) {
435 if(access(dir
, R_OK
) == -1 && errno
== ENOENT
) {
436 fprintf(stderr
, "error opening dir %s\n", dir
);
439 FILE *info
= 0, *out
= 0;
441 snprintf(buf
, sizeof buf
, "%s/agsprite.info", dir
);
442 info
= fopen(buf
, "r");
444 fprintf(stderr
, "error opening %s\n", buf
);
449 while(fgets(buf
, sizeof buf
, info
)) {
451 if(buf
[0] == '#') continue; /* comment */
453 p
= strrchr(buf
, '\n');
456 if(p
> buf
&& p
[-1] == '\r') p
[-1] = 0;
458 p
= strchr(buf
, '=');
460 fprintf(stderr
, "syntax error on line %d of agsprite.info\n", line
);
466 } else if(!strcmp("info", buf
)) {
467 } else if(!strcmp("spritecacheversion", buf
)) {
468 sf
.version
= atoi(p
);
469 } else if(!strcmp("spritecount", buf
)) {
470 sf
.num_sprites
= atoi(p
);
471 } else if(!strcmp("id", buf
)) {
473 } else if(!strcmp("palette", buf
)) {
476 snprintf(buf2
, sizeof buf2
, "%s/%s", dir
, p
);
477 FILE *pal
= fopen(buf2
, "r");
479 fprintf(stderr
, "error opening %s\n", buf2
);
482 sf
.palette
= malloc(256*3);
483 fread(sf
.palette
, 1, 256*3, pal
);
487 if(strcmp(buf
, "0")) {
488 fprintf(stderr
, "unexpected keyword %s\n", buf
);
491 out
= fopen(file
, "w");
493 fprintf(stderr
, "error opening %s\n", file
);
496 /* default to compressed, if possible, and unless overridden */
497 sf
.compressed
= !(flags
&FL_UNCOMPRESSED
);
498 SpriteFile_write_header(out
, &sf
);
502 int n
= filenr
= atoi(buf
);
504 /* FIXME: use sscanf */
505 if(strstr(p
, "_08_")) org_bpp
= 1;
506 else if(strstr(p
, "_16_")) org_bpp
= 2;
507 if(flags
& FL_VERBOSE
) printf("adding %d (%s)\n", n
, p
);
508 if(debug_pic
== n
) breakpoint();
510 while(sf
.num_sprites
< n
) SpriteFile_add(out
, &sf
, &(ImageData
){0});
512 snprintf(fnbuf
, sizeof fnbuf
, "%s/%s", dir
, p
);
513 FILE *spr
= fopen(fnbuf
, "r");
515 fprintf(stderr
, "error opening %s\n", fnbuf
);
520 (sf
.version
== 4 && org_bpp
!= 1) ||
521 (sf
.version
>= 4 && org_bpp
== 1);
522 if(!read_tga(spr
, &data
, skip_palette
)) {
523 fprintf(stderr
, "error reading tga file %s\n", p
);
526 tga_to_ags(&data
, org_bpp
);
527 SpriteFile_add(out
, &sf
, &data
);
532 SpriteFile_finalize(out
, &sf
);
538 static int sprindex(char* infile
, char* outfile
) {
542 ret
= AF_open(&f
, infile
);
544 fprintf(stderr
, "error opening %s\n", infile
);
547 if(flags
& FL_VERBOSE
) printf("processing spritefile TOC...\n");
548 ret
= SpriteFile_read(&f
, &sf
);
550 fprintf(stderr
, "error reading spritefile %s\n", infile
);
553 FILE *out
= fopen(outfile
, "w");
555 fprintf(stderr
, "error opening outfile %s\n", outfile
);
558 ret
= SpriteFile_write_sprindex(&f
, &sf
, out
);
564 static int parse_argstr(char *arg
)
566 const struct flagmap
{
574 { 'u', FL_UNCOMPRESSED
},
576 { 'H', FL_HICOLOR_SIMPLE
},
582 for(i
=0;map
[i
].chr
;++i
)
583 if(map
[i
].chr
== *arg
) {
584 flags
|= map
[i
].flag
;
594 static int usage(char *a
) {
595 printf( "%s ACTIONSTR acsprset.spr DIR\n"
596 "ACTIONSTR can be:\n"
599 "i - create sprindex.dat from .spr\n"
600 "optionally followed by option characters.\n\n"
601 "option characters:\n"
602 "v - be verbose (both)\n"
603 "u - don't use RLE compression if v >= 6 (pack)\n"
604 "h - store all 32bit sprites as 16bit (pack)\n"
605 "H - same, but only when alpha unused (pack)\n"
608 "extracts all sprites from acsprset.spr to DIR\n"
609 "due to the way sprite packs work, for some versions\n"
610 "8 bit images are stored without palette (dynamically\n"
611 "assigned during game). in such a case a random palette\n"
614 "packs files in DIR to acsprset.spr\n"
615 "image files need to be in tga format\n\n"
616 "sprite index mode:\n"
617 "here DIR parameter is repurposed to actually mean output file.\n"
618 "a sprindex.dat file corresponding to acsprset.spr param is created.\n\n"
620 "%s xv acsprset.spr IMAGES/\n"
621 "%s cu test.spr IMAGES/\n"
622 "%s i repack.spr FILES/sprindex.dat\n"
627 int main(int argc
, char **argv
) {
629 if(argc
!= 4 || !(flags
= parse_argstr(argv
[1]))
630 || !((flags
& FL_EXTRACT
) || (flags
& FL_PACK
) || (flags
& FL_SPRINDEX
))
631 || ((flags
&FL_EXTRACT
)&&(flags
&FL_PACK
)) )
632 return usage(argv
[0]);
634 char* file
= argv
[2];
636 if(getenv("DEBUG")) debug_pic
= atoi(getenv("DEBUG"));
638 if(flags
& FL_EXTRACT
) return extract(file
, dir
);
639 else if(flags
& FL_SPRINDEX
) return sprindex(file
, dir
);
640 else return pack(file
, dir
);