alice-ar: support extracting ALD and AFA files

Support whole archive, by-index and by-name extraction of files from ALD
and AFA archives.
This commit is contained in:
Nunuhara Cabbage
2020-04-24 22:49:42 -07:00
parent 15ef8caa91
commit 99b375acce
6 changed files with 417 additions and 53 deletions
+162 -9
View File
@@ -17,9 +17,17 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#include <getopt.h>
#include <limits.h>
#include <libgen.h>
#include "system4.h"
#include "system4/afa.h"
#include "system4/ald.h"
#include "file.h"
#include "system4/string.h"
#include "system4/utfsjis.h"
@@ -52,6 +60,144 @@ enum {
LOPT_FORCE,
};
// dirname is allowed to return a pointer to static memory OR modify its input.
// This works around the braindamage by ALWAYS returning a pointer to static
// memory, at the cost of a string copy.
char *xdirname(const char *path)
{
static char buf[PATH_MAX];
strncpy(buf, path, PATH_MAX-1);
return dirname(buf);
}
char *xbasename(const char *path)
{
static char buf[PATH_MAX];
strncpy(buf, path, PATH_MAX-1);
return basename(buf);
}
static struct archive *open_ald_archive(const char *path, int *error)
{
int count = 0;
char *dir_name = xdirname(path);
char *base_name = xbasename(path);
char *ald_filenames[ALD_FILEMAX];
int prefix_len = strlen(base_name) - 5;
if (prefix_len <= 0)
return NULL;
memset(ald_filenames, 0, sizeof(char*) * ALD_FILEMAX);
DIR *dir;
struct dirent *d;
char filepath[PATH_MAX];
if (!(dir = opendir(dir_name))) {
ERROR("Failed to open directory: %s", path);
}
while ((d = readdir(dir))) {
printf("checking %s\n", d->d_name);
int len = strlen(d->d_name);
if (len < prefix_len + 5 || strcasecmp(d->d_name+len-4, ".ald"))
continue;
if (strncasecmp(d->d_name, base_name, prefix_len))
continue;
int dno = toupper(*(d->d_name+len-5)) - 'A';
if (dno < 0 || dno >= ALD_FILEMAX)
continue;
snprintf(filepath, PATH_MAX-1, "%s/%s", dir_name, d->d_name);
ald_filenames[dno] = strdup(filepath);
count = max(count, dno+1);
}
struct archive *ar = ald_open(ald_filenames, count, ARCHIVE_MMAP, error);
for (int i = 0; i < ALD_FILEMAX; i++) {
free(ald_filenames[i]);
}
return ar;
}
static struct archive *open_archive(const char *path, int *error)
{
size_t len = strlen(path);
if (len < 4)
goto err;
const char *ext = path + len - 4;
if (!strcasecmp(ext, ".ald")) {
return open_ald_archive(path, error);
} else if (!strcasecmp(ext, ".afa")) {
return (struct archive*)afa_open(path, ARCHIVE_MMAP, error);
}
err:
usage();
ERROR("Couldn't determine archive type for '%s'", path);
}
static void mkdir_for_file(const char *filename)
{
char *tmp = strdup(filename);
char *dir = dirname(tmp);
mkdir_p(dir);
free(tmp);
}
static void write_file(struct archive_data *data, const char *output_file)
{
FILE *f = NULL;
if (!output_file) {
char *u = sjis2utf(data->name, strlen(data->name));
mkdir_for_file(u);
if (!(f = fopen(u, "wb")))
ERROR("fopen failed: %s", strerror(errno));
free(u);
} else if (strcmp(output_file, "-")) {
if (!(f = fopen(output_file, "wb")))
ERROR("fopen failed: %s", strerror(errno));
}
if (fwrite(data->data, data->size, 1, f ? f : stdout) != 1)
ERROR("fwrite failed: %s", strerror(errno));
if (f)
fclose(f);
}
static void extract_all_iter(struct archive_data *data, void *_output_dir)
{
char *output_dir = _output_dir ? _output_dir : ".";
char *file_name = sjis2utf(data->name, 0);
char output_file[PATH_MAX];
snprintf(output_file, PATH_MAX, "%s/%s", output_dir, file_name);
free(file_name);
mkdir_for_file(output_file);
printf("%s\n", output_file);
if (!archive_load_file(data)) {
WARNING("Error loading file: %s", output_file);
return;
}
write_file(data, output_file);
}
static void list_all_iter(struct archive_data *data, possibly_unused void *_)
{
char *name = sjis2utf(data->name, 0);
printf("%d: %s\n", data->no, name);
free(name);
}
int main(int argc, char *argv[])
{
char *output_file = NULL;
@@ -64,7 +210,7 @@ int main(int argc, char *argv[])
bool add = false;
bool delete = false;
bool list = false;
bool force = false;
//bool force = false;
while (1) {
static struct option long_options[] = {
@@ -146,25 +292,32 @@ int main(int argc, char *argv[])
}
int error;
struct afa_archive *ar = afa_open(argv[0], ARCHIVE_MMAP, &error);
struct archive *ar = open_archive(argv[0], &error);
if (!ar) {
ERROR("Opening archive: %s", archive_strerror(error));
}
if (extract) {
WARNING("archive extraction not yet implemented");
if (file_index >= 0) {
struct archive_data *d = archive_get(ar, file_index);
if (!d)
ERROR("No file with index %d", file_index);
write_file(d, output_file);
} else if (file_name) {
char *u = utf2sjis(file_name, strlen(file_name));
struct archive_data *d = archive_get_by_name(ar, u);
write_file(d, output_file);
} else {
archive_for_each(ar, extract_all_iter, output_file);
}
} else if (add) {
WARNING("adding files to archive not yet implemented");
} else if (delete) {
WARNING("deleting files from archive not yet implemented");
} else if (list) {
for (uint32_t i = 0; i < ar->nr_files; i++) {
char *u = sjis2utf(ar->files[i].name->text, ar->files[i].name->size);
printf("%u: %s\n", i, u);
free(u);
}
archive_for_each(ar, list_all_iter, NULL);
}
archive_free(&ar->ar);
archive_free(ar);
return 0;
}