hbmap: fix iterator truncation when size_t < 32bit
[rofl0r-agsutils.git] / agsprite.c
bloba544aedba3428c07eb09dfe7ab3baf231e79ccb9
1 #include "File.h"
2 #include <unistd.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <sys/stat.h>
6 #include <errno.h>
7 #include "endianness.h"
8 #define TARGA_IMPL
9 #include "Targa.h"
10 #include "SpriteFile.h"
11 #include <assert.h>
12 #include "version.h"
13 #include "debug.h"
14 #include "DataFile.h"
15 #ifdef _WIN32
16 #include <direct.h>
17 #define MKDIR(D) mkdir(D)
18 #define PSEP '\\'
19 #else
20 #define MKDIR(D) mkdir(D, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
21 #define PSEP '/'
22 #endif
24 #define ADS ":::AGSprite " VERSION " by rofl0r:::"
26 #define FL_EXTRACT 1<<0
27 #define FL_PACK 1<<1
28 #define FL_VERBOSE 1<<2
29 #define FL_UNCOMPRESSED 1<<3
30 #define FL_HICOLOR 1<<4
31 #define FL_HICOLOR_SIMPLE (1<<5)
32 #define FL_SPRINDEX (1<<6)
34 extern unsigned char defpal[];
37 sprite file versions:
38 kSprfVersion_Uncompressed = 4,
39 kSprfVersion_Compressed = 5,
40 kSprfVersion_Last32bit = 6,
41 kSprfVersion_64bit = 10,
42 kSprfVersion_HighSpriteLimit = 11,
43 kSprfVersion_StorageFormats = 12,
46 static int debug_pic = -1, flags, filenr;
47 static unsigned char *alphaflags;
49 static int extract(char* file, char* dir) {
50 if(access(dir, R_OK) == -1 && errno == ENOENT) {
51 MKDIR(dir);
53 AF f;
54 SpriteFile sf;
55 int ret;
56 ret = AF_open(&f, file);
57 if(!ret) {
58 fprintf(stderr, "error opening %s\n", file);
59 return 1;
61 if(flags & FL_VERBOSE) printf("processing spritefile TOC...\n");
62 ret = SpriteFile_read(&f, &sf);
63 if(!ret) {
64 fprintf(stderr, "error reading spritefile %s\n", file);
65 return 1;
67 FILE *info = 0;
69 char buf[1024];
70 if(sf.palette) {
71 snprintf(buf, sizeof buf, "%s%cagsprite.pal", dir, PSEP);
72 FILE *pal = fopen(buf, "wb");
73 if(!pal) goto ferr;
74 fwrite(sf.palette, 1, 256*3, pal);
75 fclose(pal);
77 snprintf(buf, sizeof buf, "%s%cagsprite.info", dir, PSEP);
78 info = fopen(buf, "w");
79 if(!info) {
80 ferr:
81 fprintf(stderr, "error opening %s\n", buf);
82 return 1;
84 fprintf(info,
85 "info=infofile created by " ADS "\n"
86 "info=this file is needed to reconstruct acsprset.spr\n"
87 "spritecacheversion=%d\n"
88 "spritecount=%d\n"
89 "id=%d\n"
90 "palette=%s\n"
91 , sf.version, sf.num_sprites, sf.id, sf.palette ? "agsprite.pal" : ""
94 if(!sf.palette) sf.palette = defpal;
95 int i;
96 for(i=0; i<sf.num_sprites; i++) {
97 if(debug_pic == i) breakpoint();
98 ImageData d;
99 int ret = SpriteFile_extract(&f, &sf, i, &d);
100 if(ret == 1) {
101 char namebuf[64];
102 snprintf(namebuf, sizeof namebuf, "sprite%06d_%02d_%dx%d.tga", i, d.bytesperpixel*8, d.width, d.height);
103 fprintf(info, "%d=%s\n", i, namebuf);
104 if(flags & FL_VERBOSE) printf("extracting sprite %d (%s)\n", i, namebuf);
105 char filename[1024];
106 snprintf(filename, sizeof filename, "%s%c%s", dir, PSEP, namebuf);
107 if(!Targa_writefile(filename, &d, sf.palette))
108 fprintf(stderr, "error opening %s\n", filename);
109 free(d.data);
110 } else if (ret == 0) {
111 fprintf(stderr, "warning: failed to extract sprite %d\n", i);
114 fclose(info);
115 return 0;
118 static int is_upscaled_16bit(ImageData *d) {
119 int i;
120 for(i=0; i<d->data_size; i++) {
121 if(i%3 != 1) { /* topmost 3 bits appended */
122 if((d->data[i] & 7) != (d->data[i] >> 5))
123 return 0;
124 } else { /* topmost 2 bits appended */
125 if((d->data[i] & 3) != (d->data[i] >> 6))
126 return 0;
129 return 1;
132 static int rawx_to_ags16(ImageData *d, int bpp) {
133 int i, imax = d->data_size/bpp;
134 unsigned char *data = malloc(d->width*d->height*2UL), *p = data;
135 if(!data) return 0;
136 for(i=0; i<imax; i++) {
137 unsigned b = d->data[i*bpp+0];
138 unsigned g = d->data[i*bpp+1];
139 unsigned r = d->data[i*bpp+2];
140 unsigned hi = (r & ~7) | (g >> 5);
141 unsigned lo = ((g & 28) << 3) | (b >> 3);
142 *(p++) = lo;
143 *(p++) = hi;
145 free(d->data);
146 d->data = data;
147 d->bytesperpixel = 2;
148 d->data_size = d->width*d->height*2;
149 return 1;
152 static int raw24_to_ags16(ImageData *d) {
153 return rawx_to_ags16(d, 3);
155 static int raw32_to_ags16(ImageData *d) {
156 return rawx_to_ags16(d, 4);
159 static int raw24_to_32(ImageData *d) {
160 unsigned char* data = malloc(d->width*d->height*4UL), *p = data, *q = d->data;
161 if(!data) return 0;
162 int i;
163 for(i=0;i<d->width*d->height;++i) {
164 unsigned b = *(q++);
165 unsigned g = *(q++);
166 unsigned r = *(q++);
167 *(p++) = b;
168 *(p++) = g;
169 *(p++) = r;
170 /* restore transparency for "magic magenta" pixels */
171 *(p++) = "\xff\0"[!!(r == 0xff && g == 0 && b == 0xff)];
173 free(d->data);
174 d->data = data;
175 d->bytesperpixel = 4;
176 d->data_size = d->width*d->height*4;
177 return 1;
179 static int raw32_swap_alpha(ImageData *d) {
180 #if 0
181 unsigned char *p = d->data, *pe = d->data+d->data_size;
182 while(p < pe) {
183 unsigned char *q = p;
184 unsigned r = *(q++);
185 unsigned g = *(q++);
186 unsigned b = *(q++);
187 unsigned a = *(q++);
188 *(p++) = a;
189 *(p++) = r;
190 *(p++) = g;
191 *(p++) = b;
193 #endif
194 return 1;
197 #define SPF_ALPHACHANNEL 0x10
198 /* return true if alpha uses only values 0 (fully transparent)
199 or 0xff (not transparent), and transparency is only used
200 for "magic magenta".
201 if alphaflags array is provided, additionally checks that
202 the image is not marked as "uses alphachannel" in AGS datafile.
204 static int is_hicolor_candidate(ImageData *d) {
205 if(alphaflags && (alphaflags[filenr] & SPF_ALPHACHANNEL))
206 return 0;
207 unsigned char *p = d->data, *pe = d->data+d->data_size;
208 while(p < pe) {
209 unsigned b = *(p++);
210 unsigned g = *(p++);
211 unsigned r = *(p++);
212 unsigned a = *(p++);
213 switch(a) {
214 case 0xff: break;
215 case 0:
216 if(!(r == 0xff && g == 0 && b == 0xff))
217 return 0;
218 break;
219 default: return 0;
222 return 1;
224 static int tga_to_ags(ImageData *d, int org_bpp) {
225 /* convert raw image data to something acceptable for ags */
226 switch(d->bytesperpixel) {
227 case 4:
228 if(flags & FL_HICOLOR) return raw32_to_ags16(d);
229 else if((flags & FL_HICOLOR_SIMPLE) && is_hicolor_candidate(d)) {
230 if(flags & FL_VERBOSE) printf("converting %d to 16bpp\n", filenr);
231 return raw32_to_ags16(d);
232 } else return raw32_swap_alpha(d);
233 case 3:
234 if(flags & FL_HICOLOR) return raw24_to_ags16(d);
235 if(org_bpp == 2 && is_upscaled_16bit(d)) return raw24_to_ags16(d);
236 else return raw24_to_32(d);
238 return 1;
241 static int pack(char* file, char* dir) {
242 if(access(dir, R_OK) == -1 && errno == ENOENT) {
243 fprintf(stderr, "error opening dir %s\n", dir);
244 return 1;
246 FILE *info = 0, *out = 0;
247 char buf[1024];
248 snprintf(buf, sizeof buf, "%s%cagsprite.info", dir, PSEP);
249 info = fopen(buf, "r");
250 if(!info) {
251 fprintf(stderr, "error opening %s\n", buf);
252 return 1;
254 SpriteFile sf = {0};
255 int line = 0;
256 while(fgets(buf, sizeof buf, info)) {
257 ++line;
258 if(buf[0] == '#') continue; /* comment */
259 char *p;
260 p = strrchr(buf, '\n');
261 if(p) {
262 p[0] = 0;
263 if(p > buf && p[-1] == '\r') p[-1] = 0;
265 p = strchr(buf, '=');
266 if(!p) {
267 fprintf(stderr, "syntax error on line %d of agsprite.info\n", line);
268 return 1;
270 *(p++) = 0;
271 if(!out) {
272 if(0) {
273 } else if(!strcmp("info", buf)) {
274 } else if(!strcmp("spritecacheversion", buf)) {
275 sf.version = atoi(p);
276 if(sf.version > 6) {
277 fprintf(stderr, "warning: converting spritecache version %d to version 6\n", sf.version);
278 sf.version = 6;
280 } else if(!strcmp("spritecount", buf)) {
281 sf.num_sprites = atoi(p);
282 } else if(!strcmp("id", buf)) {
283 sf.id = atoi(p);
284 } else if(!strcmp("palette", buf)) {
285 if (*p) {
286 char buf2[1024];
287 snprintf(buf2, sizeof buf2, "%s%c%s", dir, PSEP, p);
288 FILE *pal = fopen(buf2, "rb");
289 if(!pal) {
290 fprintf(stderr, "error opening %s\n", buf2);
291 return 1;
293 sf.palette = malloc(256*3);
294 fread(sf.palette, 1, 256*3, pal);
295 fclose(pal);
297 } else {
298 if(strcmp(buf, "0")) {
299 fprintf(stderr, "unexpected keyword %s\n", buf);
300 return 1;
302 out = fopen(file, "wb");
303 if(!out) {
304 fprintf(stderr, "error opening %s\n", file);
305 return 1;
307 /* default to compressed, if possible, and unless overridden */
308 sf.compressed = !(flags&FL_UNCOMPRESSED);
309 /* SpriteFile_write_header also resets sf.compressed, if needed */
310 SpriteFile_write_header(out, &sf);
313 if(out) {
314 int n = filenr = atoi(buf);
315 int org_bpp = 4;
316 /* FIXME: use sscanf */
317 if(strstr(p, "_08_")) org_bpp = 1;
318 else if(strstr(p, "_16_")) org_bpp = 2;
319 if(flags & FL_VERBOSE) printf("adding %d (%s)\n", n, p);
320 if(debug_pic == n) breakpoint();
322 while(sf.num_sprites < n) SpriteFile_add(out, &sf, &(ImageData){0});
323 char fnbuf[1024];
324 snprintf(fnbuf, sizeof fnbuf, "%s%c%s", dir, PSEP, p);
325 ImageData data;
326 int skip_palette = org_bpp == 1;
327 if(!Targa_readfile(fnbuf, &data, skip_palette)) {
328 fprintf(stderr, "error reading tga file %s\n", p);
329 return 1;
331 tga_to_ags(&data, org_bpp);
332 SpriteFile_add(out, &sf, &data);
333 free(data.data);
336 SpriteFile_finalize(out, &sf);
337 fclose(out);
338 fclose(info);
339 return 0;
342 static int sprindex(char* infile, char* outfile) {
343 AF f;
344 SpriteFile sf;
345 int ret;
346 ret = AF_open(&f, infile);
347 if(!ret) {
348 fprintf(stderr, "error opening %s\n", infile);
349 return 1;
351 if(flags & FL_VERBOSE) printf("processing spritefile TOC...\n");
352 ret = SpriteFile_read(&f, &sf);
353 if(!ret) {
354 fprintf(stderr, "error reading spritefile %s\n", infile);
355 return 1;
357 FILE *out = fopen(outfile, "wb");
358 if(!out) {
359 fprintf(stderr, "error opening outfile %s\n", outfile);
360 return 1;
362 ret = SpriteFile_write_sprindex(&f, &sf, out);
363 AF_close(&f);
364 fclose(out);
365 return !ret;
368 static int parse_argstr(char *arg)
370 const struct flagmap {
371 char chr;
372 int flag;
373 } map[] = {
374 { 'x', FL_EXTRACT},
375 { 'c', FL_PACK},
376 { 'i', FL_SPRINDEX},
377 { 'v', FL_VERBOSE},
378 { 'u', FL_UNCOMPRESSED},
379 { 'h', FL_HICOLOR},
380 { 'H', FL_HICOLOR_SIMPLE},
381 {0, 0},
383 int ret = 0, i;
384 while(*arg) {
385 int found = 0;
386 for(i=0;map[i].chr;++i)
387 if(map[i].chr == *arg) {
388 ret |= map[i].flag;
389 found = 1;
390 break;
392 if(!found) return 0;
393 ++arg;
395 return ret;
398 static int usage(char *a) {
399 printf( "%s ACTIONSTR acsprset.spr DIR [FILES]\n"
400 "ACTIONSTR can be:\n"
401 "x - extract\n"
402 "c - pack\n"
403 "i - create sprindex.dat from .spr\n"
404 "optionally followed by option characters.\n\n"
405 "option characters:\n"
406 "v - be verbose (both)\n"
407 "u - don't use RLE compression if v >= 6 (pack)\n"
408 "h - store all 32bit sprites as 16bit (pack)\n"
409 "H - same, but only when alpha unused (pack)\n"
410 " accepts an optional FILES parameter, which denotes\n"
411 " the FILES directory with extracted game contents.\n"
412 " if provided, takes information whether alpha is used\n"
413 " from the game.\n"
414 "\n"
415 "extract mode:\n"
416 "extracts all sprites from acsprset.spr to DIR\n"
417 "due to the way sprite packs work, for some versions\n"
418 "8 bit images are stored without palette (dynamically\n"
419 "assigned during game). in such a case a standard palette\n"
420 "is assigned.\n\n"
421 "pack mode:\n"
422 "packs files in DIR to acsprset.spr\n"
423 "image files need to be in tga format\n\n"
424 "sprite index mode:\n"
425 "here DIR parameter is repurposed to actually mean output file.\n"
426 "a sprindex.dat file corresponding to acsprset.spr param is created.\n\n"
427 "examples:\n"
428 "%s xv acsprset.spr IMAGES/\n"
429 "%s cu test.spr IMAGES/\n"
430 "%s i repack.spr FILES/sprindex.dat\n"
431 , a, a, a, a);
432 return 1;
435 int main(int argc, char **argv) {
437 if(argc < 4 || !(flags = parse_argstr(argv[1]))
438 || !((flags & FL_EXTRACT) || (flags & FL_PACK) || (flags & FL_SPRINDEX))
439 || ((flags&FL_EXTRACT)&&(flags&FL_PACK)) )
440 return usage(argv[0]);
442 char* file = argv[2];
443 char *dir = argv[3];
445 if(flags & FL_HICOLOR_SIMPLE && argc > 4) {
446 char *fdir = argv[4];
447 ADF a_b, *a = &a_b;
448 char fnbuf[512];
449 if(!ADF_find_datafile(fdir, fnbuf, sizeof(fnbuf))) {
450 e_1:
451 fprintf(stderr, "failed to find/open/read datafile in %s\n", fdir);
452 return 1;
454 enum ADF_open_error aoe = ADF_open(a, fnbuf);
455 if(aoe != AOE_success && aoe <= AOE_gamebase) {
456 fprintf(stderr, "failed to open/process data file: %s\n", AOE2str(aoe));
457 return 1;
458 } else if (aoe != AOE_success) {
459 fprintf(stderr, "warning: failed to process some non-essential parts (%s) of gamefile, probably from a newer game format\n", AOE2str(aoe));
462 off_t off = ADF_get_spriteflagsstart(a);
463 unsigned nsprites = ADF_get_spritecount(a);
464 ADF_close(a);
466 FILE *f = fopen(fnbuf, "rb");
467 if(!f) goto e_1;
468 fseeko(f, off, SEEK_SET);
469 alphaflags = malloc(nsprites);
470 if(nsprites != fread(alphaflags, 1, nsprites, f)) goto e_1;
471 fclose(f);
474 if(getenv("DEBUG")) debug_pic = atoi(getenv("DEBUG"));
476 if(flags & FL_EXTRACT) return extract(file, dir);
477 else if(flags & FL_SPRINDEX) return sprindex(file, dir);
478 else return pack(file, dir);