agsprite: detect comments
[rofl0r-agsutils.git] / agsprite.c
blobea2c279e22af55dee3e5328419a7a4cf7f1f5412
1 #include "File.h"
2 #include "SpriteFile.h"
3 #include <unistd.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <sys/stat.h>
7 #include <errno.h>
8 #include "endianness.h"
9 #include "BitmapFuncs.h"
10 #include "Targa.h"
11 #include "rle.h"
12 #include <assert.h>
13 #include "version.h"
14 #define ADS ":::AGSprite " VERSION " by rofl0r:::"
16 #ifdef __x86_64__
17 #define breakpoint() __asm__("int3")
18 #else
19 #define breakpoint() do{}while(0)
20 #endif
22 #define FL_EXTRACT 1<<0
23 #define FL_PACK 1<<1
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)
34 int i;
35 for(i=0; i<ncols; ++i)
36 if(palette[i] == color) return i;
37 return -1;
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,
51 *q = d->data;
52 if(!out) return 0;
53 while(p < pe) {
54 unsigned r,g,b,lo,hi;
55 lo = *(q++);
56 hi = *(q++);
57 rgb565_to_888(lo, hi, &r, &g, &b);
58 *(p++) = b;
59 *(p++) = g;
60 *(p++) = r;
62 free(d->data);
63 d->data = out;
64 d->data_size = outsz;
65 d->bytesperpixel = 3;
66 return 1;
69 static int create_palette_pic(const ImageData* d, unsigned *palette, unsigned char **data)
71 int ret = 0;
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);
75 unsigned i, a = 0;
76 while(p < q && o < e) {
77 unsigned col, r, g, b;
78 switch(d->bytesperpixel) {
79 case 2: {
80 unsigned lo = *(p++);
81 unsigned hi = *(p++);
82 rgb565_to_888(lo, hi, &r, &g, &b);
83 break; }
84 case 3:
85 case 4:
86 b = *(p++);
87 g = *(p++);
88 r = *(p++);
89 if(d->bytesperpixel == 4) a = *(p++);
90 break;
91 default: assert(0);
93 col = a << 24 | r << 16 | g << 8 | b;
94 int n = lookup_palette(col, palette, ret);
95 if(n < 0) {
96 if(ret == 256) {
97 free(*data);
98 *data = 0;
99 return -1;
101 n = ret;
102 palette[ret++] = col;
104 *(o++) = n;
106 return ret;
109 static void write_tga(char *name, ImageData* d, unsigned char *palette)
111 unsigned pal[256];
112 unsigned char *paldata = 0, *data = d->data;
113 unsigned bpp = d->bytesperpixel;
114 unsigned data_size = d->data_size;
115 int palcount = 256;
116 int i;
118 FILE *f = fopen(name, "w");
119 if(!f) {
120 fprintf(stderr, "error opening %s\n", name);
121 return;
124 if(bpp == 1) {
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 */
135 bpp = 1;
136 data = paldata;
137 data_size = d->width*d->height;
138 } else if(bpp == 2 && convert_16_to_24(d)) {
139 bpp = 3;
140 data = d->data;
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);
145 int use_rle = 0;
146 if(rle_data && rle_data_size < data_size) {
147 data_size = rle_data_size;
148 data = rle_data;
149 use_rle = 1;
151 struct TargaHeader hdr = {
152 .idlength = 0,
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,
160 .x_origin = 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);
168 unsigned tmp;
169 if(bpp == 1) for(i=0; i<palcount; ++i) {
170 tmp = le32(pal[i]);
171 fwrite(&tmp, 1, 3, f);
173 fwrite(data, 1, data_size, f);
174 fclose(f);
175 free(paldata);
176 free(rle_data);
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);
183 AF f;
184 SpriteFile sf;
185 int ret;
186 ret = AF_open(&f, file);
187 if(!ret) {
188 fprintf(stderr, "error opening %s\n", file);
189 return 1;
191 if(flags & FL_VERBOSE) printf("processing spritefile TOC...\n");
192 ret = SpriteFile_read(&f, &sf);
193 if(!ret) {
194 fprintf(stderr, "error reading spritefile %s\n", file);
195 return 1;
197 FILE *info = 0;
199 char buf[1024];
200 if(sf.palette) {
201 snprintf(buf, sizeof buf, "%s/agsprite.pal", dir);
202 FILE *pal = fopen(buf, "w");
203 if(!pal) goto ferr;
204 fwrite(sf.palette, 1, 256*3, pal);
205 fclose(pal);
207 snprintf(buf, sizeof buf, "%s/agsprite.info", dir);
208 info = fopen(buf, "w");
209 if(!info) {
210 ferr:
211 fprintf(stderr, "error opening %s\n", buf);
212 return 1;
214 fprintf(info,
215 "info=infofile created by " ADS "\n"
216 "info=this file is needed to reconstruct acsprset.spr\n"
217 "spritecacheversion=%d\n"
218 "spritecount=%d\n"
219 "id=%d\n"
220 "palette=%s\n"
221 , sf.version, sf.num_sprites, sf.id, sf.palette ? "agsprite.pal" : ""
224 int i;
225 for(i=0; i<sf.num_sprites; i++) {
226 if(debug_pic == i) breakpoint();
227 ImageData d;
228 if(SpriteFile_extract(&f, &sf, i, &d)) {
229 char namebuf[64];
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);
233 char filename[1024];
234 snprintf(filename, sizeof filename, "%s/%s", dir, namebuf);
235 write_tga(filename, &d, sf.palette);
236 free(d.data);
239 fclose(info);
240 return 0;
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))
253 fs -= sizeof ftr;
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);
263 fs -= ftello(f);
264 unsigned char *data = malloc(fs), *palette = 0;
265 unsigned palsz = 0;
266 fread(data, 1, fs, f);
267 if(hdr.colourmaptype) {
268 palette = data;
269 palsz = hdr.colourmaplength * hdr.colourmapdepth/8;
270 if(fs <= palsz) return 0;
271 data += palsz;
272 fs -= palsz;
274 unsigned char *workdata = 0;
275 unsigned tmp;
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);
282 break;
283 case TIT_COLOR_MAPPED:
284 case TIT_TRUE_COLOR:
285 workdata = data;
286 break;
287 default:
288 return 0;
290 idata->width = hdr.width;
291 idata->height = hdr.height;
292 if(skip_palette)
293 idata->bytesperpixel = hdr.bitsperpixel/8;
294 else
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];
309 } else {
310 memcpy(idata->data, workdata, tmp);
312 if(workdata != data) free(workdata);
313 if(palette) free(palette);
314 else free(data);
315 return 1;
318 static int is_upscaled_16bit(ImageData *d) {
319 int i;
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))
323 return 0;
324 } else { /* topmost 2 bits appended */
325 if((d->data[i] & 3) != (d->data[i] >> 6))
326 return 0;
329 return 1;
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;
335 if(!data) return 0;
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);
342 *(p++) = lo;
343 *(p++) = hi;
345 free(d->data);
346 d->data = data;
347 d->bytesperpixel = 2;
348 d->data_size = d->width*d->height*2;
349 return 1;
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;
361 if(!data) return 0;
362 int i;
363 for(i=0;i<d->width*d->height;++i) {
364 unsigned b = *(q++);
365 unsigned g = *(q++);
366 unsigned r = *(q++);
367 *(p++) = b;
368 *(p++) = g;
369 *(p++) = r;
370 /* restore transparency for "magic magenta" pixels */
371 *(p++) = "\xff\0"[!!(r == 0xff && g == 0 && b == 0xff)];
373 free(d->data);
374 d->data = data;
375 d->bytesperpixel = 4;
376 d->data_size = d->width*d->height*4;
377 return 1;
379 static int raw32_swap_alpha(ImageData *d) {
380 #if 0
381 unsigned char *p = d->data, *pe = d->data+d->data_size;
382 while(p < pe) {
383 unsigned char *q = p;
384 unsigned r = *(q++);
385 unsigned g = *(q++);
386 unsigned b = *(q++);
387 unsigned a = *(q++);
388 *(p++) = a;
389 *(p++) = r;
390 *(p++) = g;
391 *(p++) = b;
393 #endif
394 return 1;
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;
401 while(p < pe) {
402 unsigned b = *(p++);
403 unsigned g = *(p++);
404 unsigned r = *(p++);
405 unsigned a = *(p++);
406 switch(a) {
407 case 0xff: break;
408 case 0:
409 if(!(r == 0xff && g == 0 && b == 0xff))
410 return 0;
411 break;
412 default: return 0;
415 return 1;
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) {
420 case 4:
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);
426 case 3:
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);
431 return 1;
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);
437 return 1;
439 FILE *info = 0, *out = 0;
440 char buf[1024];
441 snprintf(buf, sizeof buf, "%s/agsprite.info", dir);
442 info = fopen(buf, "r");
443 if(!info) {
444 fprintf(stderr, "error opening %s\n", buf);
445 return 1;
447 SpriteFile sf = {0};
448 int line = 0;
449 while(fgets(buf, sizeof buf, info)) {
450 ++line;
451 if(buf[0] == '#') continue; /* comment */
452 char *p;
453 p = strrchr(buf, '\n');
454 if(p) {
455 p[0] = 0;
456 if(p > buf && p[-1] == '\r') p[-1] = 0;
458 p = strchr(buf, '=');
459 if(!p) {
460 fprintf(stderr, "syntax error on line %d of agsprite.info\n", line);
461 return 1;
463 *(p++) = 0;
464 if(!out) {
465 if(0) {
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)) {
472 sf.id = atoi(p);
473 } else if(!strcmp("palette", buf)) {
474 if (*p) {
475 char buf2[1024];
476 snprintf(buf2, sizeof buf2, "%s/%s", dir, p);
477 FILE *pal = fopen(buf2, "r");
478 if(!pal) {
479 fprintf(stderr, "error opening %s\n", buf2);
480 return 1;
482 sf.palette = malloc(256*3);
483 fread(sf.palette, 1, 256*3, pal);
484 fclose(pal);
486 } else {
487 if(strcmp(buf, "0")) {
488 fprintf(stderr, "unexpected keyword %s\n", buf);
489 return 1;
491 out = fopen(file, "w");
492 if(!out) {
493 fprintf(stderr, "error opening %s\n", file);
494 return 1;
496 /* default to compressed, if possible, and unless overridden */
497 sf.compressed = !(flags&FL_UNCOMPRESSED);
498 SpriteFile_write_header(out, &sf);
501 if(out) {
502 int n = filenr = atoi(buf);
503 int org_bpp = 4;
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});
511 char fnbuf[1024];
512 snprintf(fnbuf, sizeof fnbuf, "%s/%s", dir, p);
513 FILE *spr = fopen(fnbuf, "r");
514 if(!spr) {
515 fprintf(stderr, "error opening %s\n", fnbuf);
516 return 1;
518 ImageData data;
519 int skip_palette =
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);
524 return 1;
526 tga_to_ags(&data, org_bpp);
527 SpriteFile_add(out, &sf, &data);
528 free(data.data);
529 fclose(spr);
532 SpriteFile_finalize(out, &sf);
533 fclose(out);
534 fclose(info);
535 return 0;
538 static int sprindex(char* infile, char* outfile) {
539 AF f;
540 SpriteFile sf;
541 int ret;
542 ret = AF_open(&f, infile);
543 if(!ret) {
544 fprintf(stderr, "error opening %s\n", infile);
545 return 1;
547 if(flags & FL_VERBOSE) printf("processing spritefile TOC...\n");
548 ret = SpriteFile_read(&f, &sf);
549 if(!ret) {
550 fprintf(stderr, "error reading spritefile %s\n", infile);
551 return 1;
553 FILE *out = fopen(outfile, "w");
554 if(!out) {
555 fprintf(stderr, "error opening outfile %s\n", outfile);
556 return 1;
558 ret = SpriteFile_write_sprindex(&f, &sf, out);
559 AF_close(&f);
560 fclose(out);
561 return !ret;
564 static int parse_argstr(char *arg)
566 const struct flagmap {
567 char chr;
568 int flag;
569 } map[] = {
570 { 'x', FL_EXTRACT},
571 { 'c', FL_PACK},
572 { 'i', FL_SPRINDEX},
573 { 'v', FL_VERBOSE},
574 { 'u', FL_UNCOMPRESSED},
575 { 'h', FL_HICOLOR},
576 { 'H', FL_HICOLOR_SIMPLE},
577 {0, 0},
579 int flags = 0, i;
580 while(*arg) {
581 int found = 0;
582 for(i=0;map[i].chr;++i)
583 if(map[i].chr == *arg) {
584 flags |= map[i].flag;
585 found = 1;
586 break;
588 if(!found) return 0;
589 ++arg;
591 return flags;
594 static int usage(char *a) {
595 printf( "%s ACTIONSTR acsprset.spr DIR\n"
596 "ACTIONSTR can be:\n"
597 "x - extract\n"
598 "c - pack\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"
606 "\n"
607 "extract mode:\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"
612 "is generated.\n\n"
613 "pack mode:\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"
619 "examples:\n"
620 "%s xv acsprset.spr IMAGES/\n"
621 "%s cu test.spr IMAGES/\n"
622 "%s i repack.spr FILES/sprindex.dat\n"
623 , a, a, a, a);
624 return 1;
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];
635 char *dir = argv[3];
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);