7 #include "endianness.h"
10 #include "SpriteFile.h"
17 #define MKDIR(D) mkdir(D)
20 #define MKDIR(D) mkdir(D, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
24 #define ADS ":::AGSprite " VERSION " by rofl0r:::"
26 #define FL_EXTRACT 1<<0
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
[];
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
) {
56 ret
= AF_open(&f
, file
);
58 fprintf(stderr
, "error opening %s\n", file
);
61 if(flags
& FL_VERBOSE
) printf("processing spritefile TOC...\n");
62 ret
= SpriteFile_read(&f
, &sf
);
64 fprintf(stderr
, "error reading spritefile %s\n", file
);
71 snprintf(buf
, sizeof buf
, "%s%cagsprite.pal", dir
, PSEP
);
72 FILE *pal
= fopen(buf
, "wb");
74 fwrite(sf
.palette
, 1, 256*3, pal
);
77 snprintf(buf
, sizeof buf
, "%s%cagsprite.info", dir
, PSEP
);
78 info
= fopen(buf
, "w");
81 fprintf(stderr
, "error opening %s\n", buf
);
85 "info=infofile created by " ADS
"\n"
86 "info=this file is needed to reconstruct acsprset.spr\n"
87 "spritecacheversion=%d\n"
91 , sf
.version
, sf
.num_sprites
, sf
.id
, sf
.palette
? "agsprite.pal" : ""
94 if(!sf
.palette
) sf
.palette
= defpal
;
96 for(i
=0; i
<sf
.num_sprites
; i
++) {
97 if(debug_pic
== i
) breakpoint();
99 int ret
= SpriteFile_extract(&f
, &sf
, i
, &d
);
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
);
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
);
110 } else if (ret
== 0) {
111 fprintf(stderr
, "warning: failed to extract sprite %d\n", i
);
118 static int is_upscaled_16bit(ImageData
*d
) {
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))
124 } else { /* topmost 2 bits appended */
125 if((d
->data
[i
] & 3) != (d
->data
[i
] >> 6))
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
;
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);
147 d
->bytesperpixel
= 2;
148 d
->data_size
= d
->width
*d
->height
*2;
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
;
163 for(i
=0;i
<d
->width
*d
->height
;++i
) {
170 /* restore transparency for "magic magenta" pixels */
171 *(p
++) = "\xff\0"[!!(r
== 0xff && g
== 0 && b
== 0xff)];
175 d
->bytesperpixel
= 4;
176 d
->data_size
= d
->width
*d
->height
*4;
179 static int raw32_swap_alpha(ImageData
*d
) {
181 unsigned char *p
= d
->data
, *pe
= d
->data
+d
->data_size
;
183 unsigned char *q
= p
;
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
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
))
207 unsigned char *p
= d
->data
, *pe
= d
->data
+d
->data_size
;
216 if(!(r
== 0xff && g
== 0 && b
== 0xff))
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
) {
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
);
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
);
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
);
246 FILE *info
= 0, *out
= 0;
248 snprintf(buf
, sizeof buf
, "%s%cagsprite.info", dir
, PSEP
);
249 info
= fopen(buf
, "r");
251 fprintf(stderr
, "error opening %s\n", buf
);
256 while(fgets(buf
, sizeof buf
, info
)) {
258 if(buf
[0] == '#') continue; /* comment */
260 p
= strrchr(buf
, '\n');
263 if(p
> buf
&& p
[-1] == '\r') p
[-1] = 0;
265 p
= strchr(buf
, '=');
267 fprintf(stderr
, "syntax error on line %d of agsprite.info\n", line
);
273 } else if(!strcmp("info", buf
)) {
274 } else if(!strcmp("spritecacheversion", buf
)) {
275 sf
.version
= atoi(p
);
277 fprintf(stderr
, "warning: converting spritecache version %d to version 6\n", sf
.version
);
280 } else if(!strcmp("spritecount", buf
)) {
281 sf
.num_sprites
= atoi(p
);
282 } else if(!strcmp("id", buf
)) {
284 } else if(!strcmp("palette", buf
)) {
287 snprintf(buf2
, sizeof buf2
, "%s%c%s", dir
, PSEP
, p
);
288 FILE *pal
= fopen(buf2
, "rb");
290 fprintf(stderr
, "error opening %s\n", buf2
);
293 sf
.palette
= malloc(256*3);
294 fread(sf
.palette
, 1, 256*3, pal
);
298 if(strcmp(buf
, "0")) {
299 fprintf(stderr
, "unexpected keyword %s\n", buf
);
302 out
= fopen(file
, "wb");
304 fprintf(stderr
, "error opening %s\n", file
);
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
);
314 int n
= filenr
= atoi(buf
);
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});
324 snprintf(fnbuf
, sizeof fnbuf
, "%s%c%s", dir
, PSEP
, p
);
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
);
331 tga_to_ags(&data
, org_bpp
);
332 SpriteFile_add(out
, &sf
, &data
);
336 SpriteFile_finalize(out
, &sf
);
342 static int sprindex(char* infile
, char* outfile
) {
346 ret
= AF_open(&f
, infile
);
348 fprintf(stderr
, "error opening %s\n", infile
);
351 if(flags
& FL_VERBOSE
) printf("processing spritefile TOC...\n");
352 ret
= SpriteFile_read(&f
, &sf
);
354 fprintf(stderr
, "error reading spritefile %s\n", infile
);
357 FILE *out
= fopen(outfile
, "wb");
359 fprintf(stderr
, "error opening outfile %s\n", outfile
);
362 ret
= SpriteFile_write_sprindex(&f
, &sf
, out
);
368 static int parse_argstr(char *arg
)
370 const struct flagmap
{
378 { 'u', FL_UNCOMPRESSED
},
380 { 'H', FL_HICOLOR_SIMPLE
},
386 for(i
=0;map
[i
].chr
;++i
)
387 if(map
[i
].chr
== *arg
) {
398 static int usage(char *a
) {
399 printf( "%s ACTIONSTR acsprset.spr DIR [FILES]\n"
400 "ACTIONSTR can be:\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"
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"
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"
428 "%s xv acsprset.spr IMAGES/\n"
429 "%s cu test.spr IMAGES/\n"
430 "%s i repack.spr FILES/sprindex.dat\n"
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];
445 if(flags
& FL_HICOLOR_SIMPLE
&& argc
> 4) {
446 char *fdir
= argv
[4];
449 if(!ADF_find_datafile(fdir
, fnbuf
, sizeof(fnbuf
))) {
451 fprintf(stderr
, "failed to find/open/read datafile in %s\n", fdir
);
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
));
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
);
466 FILE *f
= fopen(fnbuf
, "rb");
468 fseeko(f
, off
, SEEK_SET
);
469 alphaflags
= malloc(nsprites
);
470 if(nsprites
!= fread(alphaflags
, 1, nsprites
, f
)) goto e_1
;
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
);