1 /* fjfix is a tool for handling FileJoin (FJSYS) archives.
3 In addition to pack/unpack functionality fixes file index on existing
4 archives to work with either WINE, Windows or POSIX string collation
5 order, allowing poorly coded applications run in WINE.
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
22 #define VERSION "<devel>"
27 #define strcasecmp lstrcmpiA
31 #define mkdir(n, p) mkdir(n)
44 unsigned char padding
[FJHDR_SZ
- 8 - sizeof(unsigned)*3];
57 char fjname
[PATH_MAX
];
71 struct fjfile
* fjfile
;
76 enum {FJFILE_VERBOSE
=0x1};
78 typedef struct fjfile
* fjfile_t
;
79 typedef struct fjentry
* fjentry_t
;
81 static void badmsg(const char* name
, const char* msg
)
83 fprintf(stderr
, "Bad FJ archive '%s': %s\n", name
, msg
);
86 fjfile_t
fjfile_open(const char* name
)
88 fjfile_t s
= malloc(sizeof(*s
));
90 if (strlen(name
) > sizeof(s
->fjname
))
92 strcpy(s
->fjname
, name
);
96 s
->nent
= s
->tabsz
= 0;
100 s
->file
= fopen(name
, "r+b");
102 fprintf(stderr
, "Can't open file '%s': %s\n", name
, strerror(errno
));
106 s
->hdr
= malloc(sizeof(*s
->hdr
));
107 if (fread(s
->hdr
, sizeof(*s
->hdr
), 1, s
->file
) < 1) {
108 badmsg(name
, "header too small");
112 if (strncmp(s
->hdr
->magic
, "FJSYS", 6)) {
113 badmsg(name
, "bad magic");
117 /* Limits arbitrary */
118 if (s
->hdr
->file_num
> 65535 || s
->hdr
->names_size
> 16*65536 - 1 || s
->hdr
->data_off
> (1 << 20)) {
119 badmsg(name
, "header sanity check failed");
123 s
->nent
= s
->hdr
->file_num
;
125 s
->tab
= malloc(s
->nent
*sizeof(*s
->tab
));
126 if (fread(s
->tab
, sizeof(*s
->tab
), s
->nent
, s
->file
) < s
->nent
) {
127 badmsg(name
, "incomplete entry table");
132 for (i
= 0; i
< s
->nent
; i
++) {
133 if (s
->tab
[i
].name_off
>= s
->hdr
->names_size
) {
134 badmsg(name
, "entry name offset outside name section");
139 s
->names
= malloc(s
->hdr
->names_size
);
140 if (fread(s
->names
, s
->hdr
->names_size
, 1, s
->file
) < 1) {
141 badmsg(name
, "incomplete name section");
160 const char* fjfile_name(fjfile_t s
)
165 unsigned fjfile_set_options(fjfile_t s
, unsigned flags
)
167 return s
->options
|= flags
;
170 unsigned fjfile_reset_options(fjfile_t s
, unsigned flags
)
172 return s
->options
&= ~flags
;
175 fjentry_t
fjfile_first_entry(fjfile_t fjfile
)
177 fjentry_t s
= malloc(sizeof(*s
));
185 fjentry_t
fjentry_next(fjentry_t s
)
189 if (++s
->tabind
< s
->fjfile
->nent
)
195 const char* fjentry_name(fjentry_t s
)
197 return s
->fjfile
->names
+ s
->fjfile
->tab
[s
->tabind
].name_off
;
200 unsigned fjentry_size(fjentry_t s
)
202 return s
->fjfile
->tab
[s
->tabind
].data_size
;
205 int fjentry_seek(fjentry_t s
, unsigned seekpos
)
207 if (seekpos
> s
->fjfile
->tab
[s
->tabind
].data_size
)
210 s
->seekpos
= seekpos
;
214 unsigned fjentry_read(fjentry_t s
, void* buf
, unsigned size
)
216 struct fjtabent
* te
= &s
->fjfile
->tab
[s
->tabind
];
218 if (size
> te
->data_size
- s
->seekpos
)
219 size
= te
->data_size
- s
->seekpos
;
224 if (fseek(s
->fjfile
->file
, te
->data_off
+ s
->seekpos
, SEEK_SET
) < 0)
227 if (fread(buf
, size
, 1, s
->fjfile
->file
) != 1) {
228 if (feof(s
->fjfile
->file
)) {
229 fprintf(stderr
, "Reading entry '%s': unexpected EOF at 0x%08X + %d\n",
230 s
->fjfile
->names
+ te
->name_off
, s
->seekpos
, size
);
240 fprintf(stderr
, "Reading entry '%s' failed: %s\n",
241 s
->fjfile
->names
+ te
->name_off
, strerror(errno
));
245 static int fjfile_tab_cmpswap(fjfile_t s
, unsigned i1
, unsigned i2
)
247 char* f1
= s
->names
+ s
->tab
[i1
].name_off
;
248 char* f2
= s
->names
+ s
->tab
[i2
].name_off
;
250 if (strcasecmp(f1
, f2
) > 0) {
251 if (s
->options
& FJFILE_VERBOSE
) {
252 printf("Sort: 1: '%s' > 2: '%s', swapping\n", f1
, f2
);
256 struct fjtabent t
= s
->tab
[i1
];
257 s
->tab
[i1
] = s
->tab
[i2
];
265 int fjfile_sort_index(fjfile_t s
)
269 for (swaps
= 0, passes
= 0; passes
< s
->nent
; passes
++) {
272 if (s
->options
& FJFILE_VERBOSE
) {
273 printf("Sort: DOWN\n");
277 for (sw
= 0, i
= 0; i
< s
->nent
- 1; i
++)
278 sw
+= fjfile_tab_cmpswap(s
, i
, i
+ 1);
283 if (s
->options
& FJFILE_VERBOSE
) {
284 printf("Sort: UP\n");
288 for (sw
= 0, i
= s
->nent
- 1; i
> 0; i
--)
289 sw
+= fjfile_tab_cmpswap(s
, i
- 1, i
);
296 fprintf(stderr
, "Sort: pairwise comparison not transitive, aborting\n");
301 int fjfile_write_header(fjfile_t s
)
303 if (fseek(s
->file
, 0, SEEK_SET
) < 0)
305 if (fwrite(s
->hdr
, sizeof(*s
->hdr
), 1, s
->file
) != 1)
307 if (fwrite(s
->tab
, sizeof(*s
->tab
), s
->hdr
->file_num
, s
->file
) != s
->hdr
->file_num
)
309 if (fwrite(s
->names
, s
->hdr
->names_size
, 1, s
->file
) != 1)
314 fprintf(stderr
, "Writing header tables failed: %s\n", strerror(errno
));
318 #define COPYBUF_SZ 4096
320 #define BACKUP_SUFFIX ".backup"
322 static int mkbackup(char* fname
, int progress
)
324 char backfname
[PATH_MAX
];
326 if (strlen(fname
) > sizeof(backfname
) - sizeof(BACKUP_SUFFIX
))
328 strcpy(backfname
, fname
);
329 strcat(backfname
, BACKUP_SUFFIX
);
331 FILE* backf
= fopen(backfname
, "rb");
334 f
= fopen(fname
, "rb");
339 printf("Creating backup: '%s'\n", backfname
);
343 backf
= fopen(backfname
, "wb");
348 char buf
[COPYBUF_SZ
];
349 while ((n
= fread(buf
, 1, sizeof(buf
), f
)) > 0) {
350 if (fwrite(buf
, n
, 1, backf
) != 1)
355 printf("'%s': %d kb copied\n", backfname
, sz
>> 10);
370 fprintf(stderr
, "Creating backup '%s' -> '%s' failed: %s\n",
371 fname
, backfname
, strerror(errno
));
375 static void list(fjfile_t fjf
)
379 for (ent
= fjfile_first_entry(fjf
); ent
; ent
= fjentry_next(ent
)) {
380 if (fjf
->options
& FJFILE_VERBOSE
) {
381 printf("'%s' : name_off: %d, data_size: %d, data_off: 0x%X\n",
382 fjentry_name(ent
), fjf
->tab
[ent
->tabind
].name_off
,
383 fjentry_size(ent
), fjf
->tab
[ent
->tabind
].data_off
);
385 printf("%s %d\n", fjentry_name(ent
), fjentry_size(ent
));
392 #define EXTRACT_SUFFIX ".d"
394 static char* format_destdir(const char* fjname
, const char* destdir
)
398 if (!(destdir
&& *destdir
)) {
399 const char* fname
= strrchr(fjname
, '/');
405 ddir
= malloc(strlen(fname
) + sizeof(EXTRACT_SUFFIX
));
406 sprintf(ddir
, "%s" EXTRACT_SUFFIX
, fname
);
408 size_t pl
= strlen(destdir
);
409 ddir
= malloc(pl
+ 1);
410 strcpy(ddir
, destdir
);
411 while (--pl
&& ddir
[pl
] == '/')
418 static int mkextractdir(const char* ddir
)
421 if (stat(ddir
, &dstat
) < 0) {
422 if (mkdir(ddir
, 0777) < 0) {
423 fprintf(stderr
, "Can't create destination directory '%s': %s\n",
424 ddir
, strerror(errno
));
428 if (!S_ISDIR(dstat
.st_mode
)) {
429 fprintf(stderr
, "Not a directory: '%s'\n", ddir
);
437 static int entry_extract(fjentry_t s
, const char* fname
)
439 FILE* f
= fopen(fname
, "wb");
443 printf("Extracting '%s' to '%s', size %d bytes\n",
444 fjentry_name(s
), fname
, fjentry_size(s
));
450 char buf
[COPYBUF_SZ
];
451 while ((n
= fjentry_read(s
, buf
, sizeof(buf
))) > 0) {
452 if (fwrite(buf
, n
, 1, f
) != 1)
459 if (sz
!= fjentry_size(s
)) {
460 fprintf(stderr
, "Truncated file '%s': %d of %d bytes written\n",
461 fjentry_name(s
), sz
, fjentry_size(s
));
469 fprintf(stderr
, "Can't extract to file '%s': %s\n", fname
, strerror(errno
));
473 #define MGDHDR_SZ 0x60
474 #define MGD_MAGIC "MGD "
479 char padding
[MGDHDR_SZ
- 8];
483 #define PNG_MAGIC "\x89PNG\r\n\x1A\n"
485 static int filter_mgd_test(fjentry_t s
)
490 sz
= fjentry_read(s
, &hdr
, sizeof(hdr
));
491 if (sz
!= sizeof(hdr
))
494 if (strncmp(hdr
.magic
, MGD_MAGIC
, sizeof(hdr
.magic
)))
497 char magic
[sizeof(PNG_MAGIC
) - 1];
498 sz
= fjentry_read(s
, magic
, sizeof(magic
));
499 if (strncmp(magic
, PNG_MAGIC
, sizeof(magic
)))
505 static int entry_mgd_extract(fjentry_t s
, const char* fname
)
507 FILE* f
= fopen(fname
, "wb");
513 fjentry_read(s
, &hdr
, sizeof(hdr
));
515 printf("PNG extracting '%s' to '%s', size %d bytes\n",
516 fjentry_name(s
), fname
, hdr
.png_size
);
520 char buf
[COPYBUF_SZ
];
521 while ((n
= fjentry_read(s
, buf
, sizeof(buf
))) > 0) {
522 if (sz
+ n
> hdr
.png_size
)
523 n
= hdr
.png_size
- sz
;
526 if (fwrite(buf
, n
, 1, f
) != 1)
533 if (sz
!= hdr
.png_size
) {
534 fprintf(stderr
, "PNG extract truncated file '%s': %d of %d bytes written\n",
535 fjentry_name(s
), sz
, fjentry_size(s
));
543 fprintf(stderr
, "Can't extract PNG to file '%s': %s\n", fname
, strerror(errno
));
547 enum {EX_FILTER_MGD
= 0x1};
549 static int extract(fjfile_t fjf
, const char* destdir
, unsigned filters
)
551 char fname
[PATH_MAX
];
552 char* ddir
= format_destdir(fjfile_name(fjf
), destdir
);
554 if (mkextractdir(ddir
) < 0) {
560 for (ent
= fjfile_first_entry(fjf
); ent
; ent
= fjentry_next(ent
)) {
561 if ((filters
& EX_FILTER_MGD
) && filter_mgd_test(ent
)) {
562 int l
= sprintf(fname
, "%s/%s.png", ddir
, fjentry_name(ent
));
563 char* ext
= fname
+ l
- (sizeof(".mgd.png") - 1);
564 if (ext
> fname
&& !strcasecmp(ext
, ".mgd.png"))
566 entry_mgd_extract(ent
, fname
);
568 sprintf(fname
, "%s/%s", ddir
, fjentry_name(ent
));
569 entry_extract(ent
, fname
);
577 static const char* usage
=
578 "Usage: fjfix [-fhltv] FILE [DESTDIR]\n"
579 "Fix entry index of, extract or create FJSYS (FileJoin) archive FILE.\n"
581 " -b - NO backup, otherwise creates FILE.backup before modification.\n"
582 " -f - fix entry index sort order to match system string collation order.\n"
583 " Fixes 'file not found' errors for broken collation order dependent software in WINE.\n"
584 " Uses WINE (unicode.org) collation order when run in WINE,\n"
585 " Windows (yet another non-standard) when run in Windows.\n"
586 " Native unix version uses POSIX (ASCII code) order, incompatible with both of above.\n"
588 " -l - archive entry list with byte sizes.\n"
589 " -t - test entry index, do not fix it. Enabled by default.\n"
590 " -x - extract entries as files to DESTDIR or FILE.d if DESDIR not specified.\n"
591 " -v - verbose. Print additional data which might be useful for problem analysis and reporting.\n"
592 " -G - additionally extract PNG files embedded in MGD container entries. Must be used with -x.\n"
593 "Version " VERSION
" built " __DATE__
"\n";
596 OPT_TEST
= 0x1, OPT_LIST
= 0x2, OPT_FIX
= 0x4, OPT_BACKUP
= 0x8,
601 int main(int argc
, char* argv
[0])
604 unsigned options
= OPT_TEST
| OPT_BACKUP
;
605 unsigned ext_filters
= 0;
609 while ((opt
= getopt(argc
, argv
, "Gblvtfhx")) != -1) {
612 ext_filters
|= EX_FILTER_MGD
;
615 options
&= ~OPT_BACKUP
;
627 options
|= OPT_EXTRACT
;
630 options
|= OPT_VERBOSE
;
633 fprintf(stdout
, "%s", usage
);
637 fprintf(stderr
, "Try -h for options info.\n");
642 if (optind
>= argc
) {
643 fprintf(stderr
, "FILE argument required\n");
644 fprintf(stderr
, "Try -h for usage info.\n");
648 fname
= argv
[optind
];
650 fjfile_t fjf
= fjfile_open(fname
);
652 fprintf(stderr
, "Reading '%s' failed\n", fname
);
656 if (options
& OPT_VERBOSE
) {
657 fjfile_set_options(fjf
, FJFILE_VERBOSE
);
658 printf("HEADER: file_num: %d, names_size: %d, data_off: 0x%X\n",
659 fjf
->hdr
->file_num
, fjf
->hdr
->names_size
, fjf
->hdr
->data_off
);
663 if (options
& OPT_LIST
) {
667 if (options
& OPT_EXTRACT
) {
670 if (optind
+ 1 < argc
)
671 destdir
= argv
[optind
+ 1];
673 extract(fjf
, destdir
, ext_filters
);
676 if (options
& (OPT_TEST
| OPT_FIX
)) {
677 int r
= fjfile_sort_index(fjf
);
685 fprintf(stderr
, "File index needs rebuilding\n");
688 if (needfix
&& (options
& OPT_FIX
)) {
689 if (options
& OPT_BACKUP
) {
690 if (mkbackup(fname
, 1) < 0)
693 printf("Updating index\n");
696 if (fjfile_write_header(fjf
) < 0)