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 void convert_bottom_left_tga(ImageData
*d
) {
244 size_t y
, w
= d
->width
*d
->bytesperpixel
;
245 unsigned char *swp
= malloc(w
);
247 for(y
= 0; y
< d
->height
/2; ++y
) {
248 size_t to
= w
*y
, bo
= (d
->height
-1-y
)*w
;
249 memcpy(swp
, d
->data
+ to
, w
);
250 memcpy(d
->data
+ to
, d
->data
+ bo
, w
);
251 memcpy(d
->data
+ bo
, swp
, w
);
256 static int read_tga(FILE *f
, ImageData
*idata
, int skip_palette
) {
257 struct TargaHeader hdr
;
258 struct TargaFooter ftr
;
259 fread(&hdr
, 1, sizeof hdr
, f
);
260 fseek(f
, 0, SEEK_END
);
261 off_t fs
= ftello(f
);
262 if(fs
> sizeof ftr
) {
263 fseek(f
, 0-sizeof ftr
, SEEK_END
);
264 fread(&ftr
, 1, sizeof ftr
, f
);
265 if(!memcmp(ftr
.signature
, TARGA_FOOTER_SIGNATURE
, sizeof ftr
.signature
))
268 hdr
.colourmaplength
= le16(hdr
.colourmaplength
);
269 hdr
.colourmapdepth
= le16(hdr
.colourmapdepth
);
270 hdr
.x_origin
= le16(hdr
.x_origin
);
271 hdr
.y_origin
= le16(hdr
.y_origin
);
272 hdr
.width
= le16(hdr
.width
);
273 hdr
.height
= le16(hdr
.height
);
275 fseek(f
, sizeof hdr
+ hdr
.idlength
, SEEK_SET
);
277 unsigned char *data
= malloc(fs
), *palette
= 0;
279 fread(data
, 1, fs
, f
);
280 if(hdr
.colourmaptype
) {
282 palsz
= hdr
.colourmaplength
* hdr
.colourmapdepth
/8;
283 if(fs
<= palsz
) return 0;
287 unsigned char *workdata
= 0;
289 switch(hdr
.datatypecode
) {
290 case TIT_RLE_COLOR_MAPPED
:
291 case TIT_RLE_TRUE_COLOR
:
292 tmp
= hdr
.width
*hdr
.height
*hdr
.bitsperpixel
/8;
293 workdata
= malloc(tmp
);
294 rle_decode(data
, fs
, hdr
.bitsperpixel
/8, workdata
, tmp
);
296 case TIT_COLOR_MAPPED
:
303 idata
->width
= hdr
.width
;
304 idata
->height
= hdr
.height
;
306 idata
->bytesperpixel
= hdr
.bitsperpixel
/8;
308 idata
->bytesperpixel
= hdr
.colourmapdepth
? hdr
.colourmapdepth
/8 : hdr
.bitsperpixel
/8;
310 tmp
= idata
->width
*idata
->height
*idata
->bytesperpixel
;
311 idata
->data_size
= tmp
;
312 idata
->data
= malloc(tmp
);
313 if(palette
&& !skip_palette
) {
314 unsigned i
, j
, bpp
= hdr
.colourmapdepth
/8;
315 unsigned char *p
= idata
->data
, *q
= workdata
;
316 for(i
=0; i
< idata
->width
*idata
->height
; ++i
) {
317 unsigned idx
= *(q
++);
318 if(idx
>= hdr
.colourmaplength
) return 0;
319 for(j
=0; j
< bpp
; ++j
)
320 *(p
++) = palette
[idx
*bpp
+j
];
323 memcpy(idata
->data
, workdata
, tmp
);
325 if(workdata
!= data
) free(workdata
);
326 if(palette
) free(palette
);
328 if(hdr
.y_origin
== 0) convert_bottom_left_tga(idata
);
332 static int is_upscaled_16bit(ImageData
*d
) {
334 for(i
=0; i
<d
->data_size
; i
++) {
335 if(i
%3 != 1) { /* topmost 3 bits appended */
336 if((d
->data
[i
] & 7) != (d
->data
[i
] >> 5))
338 } else { /* topmost 2 bits appended */
339 if((d
->data
[i
] & 3) != (d
->data
[i
] >> 6))
346 static int rawx_to_ags16(ImageData
*d
, int bpp
) {
347 int i
, imax
= d
->data_size
/bpp
;
348 unsigned char *data
= malloc(d
->width
*d
->height
*2), *p
= data
;
350 for(i
=0; i
<imax
; i
++) {
351 unsigned b
= d
->data
[i
*bpp
+0];
352 unsigned g
= d
->data
[i
*bpp
+1];
353 unsigned r
= d
->data
[i
*bpp
+2];
354 unsigned hi
= (r
& ~7) | (g
>> 5);
355 unsigned lo
= ((g
& 28) << 3) | (b
>> 3);
361 d
->bytesperpixel
= 2;
362 d
->data_size
= d
->width
*d
->height
*2;
366 static int raw24_to_ags16(ImageData
*d
) {
367 return rawx_to_ags16(d
, 3);
369 static int raw32_to_ags16(ImageData
*d
) {
370 return rawx_to_ags16(d
, 4);
373 static int raw24_to_32(ImageData
*d
) {
374 unsigned char* data
= malloc(d
->width
*d
->height
*4), *p
= data
, *q
= d
->data
;
377 for(i
=0;i
<d
->width
*d
->height
;++i
) {
384 /* restore transparency for "magic magenta" pixels */
385 *(p
++) = "\xff\0"[!!(r
== 0xff && g
== 0 && b
== 0xff)];
389 d
->bytesperpixel
= 4;
390 d
->data_size
= d
->width
*d
->height
*4;
393 static int raw32_swap_alpha(ImageData
*d
) {
395 unsigned char *p
= d
->data
, *pe
= d
->data
+d
->data_size
;
397 unsigned char *q
= p
;
410 /* return true if alpha uses only values 0 (fully transparent)
411 or 0xff (not transparent), and transparency is only used
412 for "magic magenta". */
413 static int is_hicolor_candidate(ImageData
*d
) {
414 unsigned char *p
= d
->data
, *pe
= d
->data
+d
->data_size
;
423 if(!(r
== 0xff && g
== 0 && b
== 0xff))
431 static int tga_to_ags(ImageData
*d
, int org_bpp
) {
432 /* convert raw image data to something acceptable for ags */
433 switch(d
->bytesperpixel
) {
435 if(flags
& FL_HICOLOR
) return raw32_to_ags16(d
);
436 else if(flags
& FL_HICOLOR_SIMPLE
&& is_hicolor_candidate(d
)) {
437 if(flags
& FL_VERBOSE
) printf("converting %d to 16bpp\n", filenr
);
438 return raw32_to_ags16(d
);
439 } else return raw32_swap_alpha(d
);
441 if(flags
& FL_HICOLOR
) return raw24_to_ags16(d
);
442 if(org_bpp
== 2 && is_upscaled_16bit(d
)) return raw24_to_ags16(d
);
443 else return raw24_to_32(d
);
448 static int pack(char* file
, char* dir
) {
449 if(access(dir
, R_OK
) == -1 && errno
== ENOENT
) {
450 fprintf(stderr
, "error opening dir %s\n", dir
);
453 FILE *info
= 0, *out
= 0;
455 snprintf(buf
, sizeof buf
, "%s/agsprite.info", dir
);
456 info
= fopen(buf
, "r");
458 fprintf(stderr
, "error opening %s\n", buf
);
463 while(fgets(buf
, sizeof buf
, info
)) {
465 if(buf
[0] == '#') continue; /* comment */
467 p
= strrchr(buf
, '\n');
470 if(p
> buf
&& p
[-1] == '\r') p
[-1] = 0;
472 p
= strchr(buf
, '=');
474 fprintf(stderr
, "syntax error on line %d of agsprite.info\n", line
);
480 } else if(!strcmp("info", buf
)) {
481 } else if(!strcmp("spritecacheversion", buf
)) {
482 sf
.version
= atoi(p
);
483 } else if(!strcmp("spritecount", buf
)) {
484 sf
.num_sprites
= atoi(p
);
485 } else if(!strcmp("id", buf
)) {
487 } else if(!strcmp("palette", buf
)) {
490 snprintf(buf2
, sizeof buf2
, "%s/%s", dir
, p
);
491 FILE *pal
= fopen(buf2
, "r");
493 fprintf(stderr
, "error opening %s\n", buf2
);
496 sf
.palette
= malloc(256*3);
497 fread(sf
.palette
, 1, 256*3, pal
);
501 if(strcmp(buf
, "0")) {
502 fprintf(stderr
, "unexpected keyword %s\n", buf
);
505 out
= fopen(file
, "w");
507 fprintf(stderr
, "error opening %s\n", file
);
510 /* default to compressed, if possible, and unless overridden */
511 sf
.compressed
= !(flags
&FL_UNCOMPRESSED
);
512 SpriteFile_write_header(out
, &sf
);
516 int n
= filenr
= atoi(buf
);
518 /* FIXME: use sscanf */
519 if(strstr(p
, "_08_")) org_bpp
= 1;
520 else if(strstr(p
, "_16_")) org_bpp
= 2;
521 if(flags
& FL_VERBOSE
) printf("adding %d (%s)\n", n
, p
);
522 if(debug_pic
== n
) breakpoint();
524 while(sf
.num_sprites
< n
) SpriteFile_add(out
, &sf
, &(ImageData
){0});
526 snprintf(fnbuf
, sizeof fnbuf
, "%s/%s", dir
, p
);
527 FILE *spr
= fopen(fnbuf
, "r");
529 fprintf(stderr
, "error opening %s\n", fnbuf
);
534 (sf
.version
== 4 && org_bpp
!= 1) ||
535 (sf
.version
>= 4 && org_bpp
== 1);
536 if(!read_tga(spr
, &data
, skip_palette
)) {
537 fprintf(stderr
, "error reading tga file %s\n", p
);
540 tga_to_ags(&data
, org_bpp
);
541 SpriteFile_add(out
, &sf
, &data
);
546 SpriteFile_finalize(out
, &sf
);
552 static int sprindex(char* infile
, char* outfile
) {
556 ret
= AF_open(&f
, infile
);
558 fprintf(stderr
, "error opening %s\n", infile
);
561 if(flags
& FL_VERBOSE
) printf("processing spritefile TOC...\n");
562 ret
= SpriteFile_read(&f
, &sf
);
564 fprintf(stderr
, "error reading spritefile %s\n", infile
);
567 FILE *out
= fopen(outfile
, "w");
569 fprintf(stderr
, "error opening outfile %s\n", outfile
);
572 ret
= SpriteFile_write_sprindex(&f
, &sf
, out
);
578 static int parse_argstr(char *arg
)
580 const struct flagmap
{
588 { 'u', FL_UNCOMPRESSED
},
590 { 'H', FL_HICOLOR_SIMPLE
},
596 for(i
=0;map
[i
].chr
;++i
)
597 if(map
[i
].chr
== *arg
) {
598 flags
|= map
[i
].flag
;
608 static int usage(char *a
) {
609 printf( "%s ACTIONSTR acsprset.spr DIR\n"
610 "ACTIONSTR can be:\n"
613 "i - create sprindex.dat from .spr\n"
614 "optionally followed by option characters.\n\n"
615 "option characters:\n"
616 "v - be verbose (both)\n"
617 "u - don't use RLE compression if v >= 6 (pack)\n"
618 "h - store all 32bit sprites as 16bit (pack)\n"
619 "H - same, but only when alpha unused (pack)\n"
622 "extracts all sprites from acsprset.spr to DIR\n"
623 "due to the way sprite packs work, for some versions\n"
624 "8 bit images are stored without palette (dynamically\n"
625 "assigned during game). in such a case a random palette\n"
628 "packs files in DIR to acsprset.spr\n"
629 "image files need to be in tga format\n\n"
630 "sprite index mode:\n"
631 "here DIR parameter is repurposed to actually mean output file.\n"
632 "a sprindex.dat file corresponding to acsprset.spr param is created.\n\n"
634 "%s xv acsprset.spr IMAGES/\n"
635 "%s cu test.spr IMAGES/\n"
636 "%s i repack.spr FILES/sprindex.dat\n"
641 int main(int argc
, char **argv
) {
643 if(argc
!= 4 || !(flags
= parse_argstr(argv
[1]))
644 || !((flags
& FL_EXTRACT
) || (flags
& FL_PACK
) || (flags
& FL_SPRINDEX
))
645 || ((flags
&FL_EXTRACT
)&&(flags
&FL_PACK
)) )
646 return usage(argv
[0]);
648 char* file
= argv
[2];
650 if(getenv("DEBUG")) debug_pic
= atoi(getenv("DEBUG"));
652 if(flags
& FL_EXTRACT
) return extract(file
, dir
);
653 else if(flags
& FL_SPRINDEX
) return sprindex(file
, dir
);
654 else return pack(file
, dir
);