From 40b67ed7f64e15ca9fa68bed6de5a8b524db82e3 Mon Sep 17 00:00:00 2001 From: Nunuhara Cabbage Date: Tue, 25 Feb 2020 20:34:51 -0800 Subject: [PATCH] Initial afa archive support Very incomplete implementation. Capable of reading the file table and not much else. Also add the "alice-ar" utility for testing. Eventually this tool should support modifying ald and afa archives. For now only supports dumping the afa file table. --- include/system4.h | 8 ++ include/system4/afa.h | 53 ++++++++++ include/system4/ald.h | 1 + src/afa.c | 223 +++++++++++++++++++++++++++++++++++++++ src/ald.c | 8 -- src/meson.build | 3 +- src/tools/ar/alice-ar.c | 170 +++++++++++++++++++++++++++++ src/tools/ar/meson.build | 8 ++ src/tools/meson.build | 1 + 9 files changed, 466 insertions(+), 9 deletions(-) create mode 100644 include/system4/afa.h create mode 100644 src/afa.c create mode 100644 src/tools/ar/alice-ar.c create mode 100644 src/tools/ar/meson.build diff --git a/include/system4.h b/include/system4.h index 69fbdad..ca418c8 100644 --- a/include/system4.h +++ b/include/system4.h @@ -93,4 +93,12 @@ char *savedir_path(const char *filename); #define SIZE_T_FMT "z" #endif +#ifdef _WIN32 +#define mmap(...) (ERROR("mmap not supported on Windows"), NULL) +#define munmap(...) ERROR("munmap not supported on Windows") +#define MAP_FAILED 0 +#else +#include +#endif + #endif /* SYSTEM4_H */ diff --git a/include/system4/afa.h b/include/system4/afa.h new file mode 100644 index 0000000..3cd17f0 --- /dev/null +++ b/include/system4/afa.h @@ -0,0 +1,53 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef SYSTEM4_AFA_H +#define SYSTEM4_AFA_H + +#include +#include +#include "system4/archive.h" + +struct string; + +struct afa_entry { + struct string *name; + uint32_t off; + uint32_t size; + uint32_t unknown0; + uint32_t unknown1; + uint32_t unknown2; +}; + +struct afa_archive { + struct archive ar; + char *filename; + size_t file_size; + uint32_t version; + uint32_t unknown; + uint32_t data_start; + uint32_t compressed_size; + uint32_t uncompressed_size; + uint32_t nr_files; + struct afa_entry *files; + uint32_t data_size; + void *mmap_ptr; + uint8_t *data; +}; + +struct afa_archive *afa_open(char *file, int flags, int *error); + +#endif /* SYSTEM4_AFA_H */ diff --git a/include/system4/ald.h b/include/system4/ald.h index ceb435f..2291e34 100644 --- a/include/system4/ald.h +++ b/include/system4/ald.h @@ -54,6 +54,7 @@ enum { ALDFILETYPE_MAX }; +// FIXME: this doesn't belong in a libsys4 header struct archive *ald[ALDFILETYPE_MAX]; /* diff --git a/src/afa.c b/src/afa.c new file mode 100644 index 0000000..040ff02 --- /dev/null +++ b/src/afa.c @@ -0,0 +1,223 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "little_endian.h" +#include "system4.h" +#include "system4/afa.h" +#include "system4/archive.h" +#include "system4/buffer.h" +#include "system4/string.h" + +static bool afa_exists(struct archive *ar, int no); +static struct archive_data *afa_get(struct archive *ar, int no); +static void afa_free_data(struct archive_data *data); +static void afa_free(struct archive *ar); + +struct archive_ops afa_archive_ops = { + .exists = afa_exists, + .get = afa_get, + .free_data = afa_free_data, + .free = afa_free, +}; + +static bool afa_exists(struct archive *_ar, int no) +{ + struct afa_archive *ar = (struct afa_archive*)_ar; + return (uint32_t)no < ar->nr_files; +} + +static struct archive_data *afa_get(struct archive *_ar, int no) +{ + struct afa_archive *ar = (struct afa_archive*)_ar; + if ((uint32_t)no >= ar->nr_files) + return NULL; + // ... + return NULL; +} + +static void afa_free_data(struct archive_data *data) +{ + +} + +static void afa_free(struct archive *_ar) +{ + struct afa_archive *ar = (struct afa_archive*)_ar; + for (uint32_t i = 0; i < ar->nr_files; i++) { + free_string(ar->files[i].name); + } + free(ar->filename); + free(ar->files); + free(ar); +} + +static bool afa_read_header(FILE *f, struct afa_archive *ar, int *error) +{ + char buf[44]; + if (fread(buf, 44, 1, f) != 1) { + *error = ARCHIVE_FILE_ERROR; + return false; + } + + fseek(f, 0, SEEK_END); + ar->file_size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (strncmp(buf, "AFAH", 4) || + strncmp(buf+8, "AlicArch", 8) || + strncmp(buf+28, "INFO", 4) || + LittleEndian_getDW((uint8_t*)buf, 4) != 0x1c) { + *error = ARCHIVE_BAD_ARCHIVE_ERROR; + return false; + } + + ar->version = LittleEndian_getDW((uint8_t*)buf, 16); + ar->unknown = LittleEndian_getDW((uint8_t*)buf, 20); + ar->data_start = LittleEndian_getDW((uint8_t*)buf, 24); + ar->compressed_size = LittleEndian_getDW((uint8_t*)buf, 32) - 16; + ar->uncompressed_size = LittleEndian_getDW((uint8_t*)buf, 36); + ar->nr_files = LittleEndian_getDW((uint8_t*)buf, 40); + + if (ar->data_start+8 >= ar->file_size) { + *error = ARCHIVE_FILE_ERROR; + return false; + } + + fseek(f, ar->data_start, SEEK_SET); + if (fread(buf, 8, 1, f) != 1) { + *error = ARCHIVE_FILE_ERROR; + return false; + } + fseek(f, 0, SEEK_SET); + + if (strncmp(buf, "DATA", 4)) { + *error = ARCHIVE_BAD_ARCHIVE_ERROR; + return false; + } + + ar->data_size = LittleEndian_getDW((uint8_t*)buf, 4); + if (ar->data_start + ar->data_size > ar->file_size) { + *error = ARCHIVE_BAD_ARCHIVE_ERROR; + return false; + } + + return true; +} + +static bool afa_read_entry(struct buffer *in, struct afa_archive *ar, struct afa_entry *entry, int *error) +{ + uint32_t name_len = buffer_read_int32(in); + entry->name = buffer_read_pascal_string(in); // NOTE: length is padded + entry->name->size = name_len; // fix length + + entry->unknown0 = buffer_read_int32(in); + entry->unknown1 = buffer_read_int32(in); + if (ar->version == 1) + entry->unknown2 = buffer_read_int32(in); + + entry->off = buffer_read_int32(in); + entry->size = buffer_read_int32(in); + return true; +} + +static bool afa_read_file_table(FILE *f, struct afa_archive *ar, int *error) +{ + uint8_t *buf = xmalloc(ar->compressed_size); + uint8_t *table = xmalloc(ar->uncompressed_size); + fseek(f, 44, SEEK_SET); + if (fread(buf, ar->compressed_size, 1, f) != 1) { + *error = ARCHIVE_FILE_ERROR; + goto exit_err; + } + + unsigned long uncompressed_size = ar->uncompressed_size; + if (uncompress(table, &uncompressed_size, buf, ar->compressed_size) != Z_OK) { + *error = ARCHIVE_BAD_ARCHIVE_ERROR; + goto exit_err; + } + + struct buffer r; + buffer_init(&r, table, ar->uncompressed_size); + ar->files = xcalloc(ar->nr_files, sizeof(struct afa_entry)); + for (uint32_t i = 0; i < ar->nr_files; i++) { + if (!afa_read_entry(&r, ar, &ar->files[i], error)) { + free(ar->files); + goto exit_err; + } + } + + free(buf); + free(table); + return true; +exit_err: + free(buf); + free(table); + return false; +} + +struct afa_archive *afa_open(char *file, int flags, int *error) +{ +#ifdef _WIN32 + flags &= ~ARCHIVE_MMAP; +#endif + FILE *fp; + struct afa_archive *ar = xcalloc(1, sizeof(struct afa_archive)); + + if (!(fp = fopen(file, "rb"))) { + *error = ARCHIVE_FILE_ERROR; + goto exit_err; + } + if (!afa_read_header(fp, ar, error)) { + fclose(fp); + goto exit_err; + } + if (!afa_read_file_table(fp, ar, error)) { + fclose(fp); + goto exit_err; + } + if (fclose(fp)) { + *error = ARCHIVE_FILE_ERROR; + goto exit_err; + } + + if (flags & ARCHIVE_MMAP) { + int fd = open(file, O_RDONLY); + if (fd < 0) { + *error = ARCHIVE_FILE_ERROR; + goto exit_err; + } + ar->mmap_ptr = mmap(0, ar->file_size, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (ar->mmap_ptr == MAP_FAILED) { + *error = ARCHIVE_FILE_ERROR; + goto exit_err; + } + ar->ar.mmapped = true; + } + ar->filename = strdup(file); + ar->ar.ops = &afa_archive_ops; + return ar; +exit_err: + free(ar); + return NULL; +} diff --git a/src/ald.c b/src/ald.c index d06c47f..ad3f351 100644 --- a/src/ald.c +++ b/src/ald.c @@ -28,14 +28,6 @@ #include "system4.h" #include "system4/ald.h" -#ifdef _WIN32 -#define mmap(...) (ERROR("mmap not supported on Windows"), NULL) -#define munmap(...) ERROR("munmap not supported on Windows") -#define MAP_FAILED 0 -#else -#include -#endif - static bool ald_exists(struct archive *ar, int no); static struct archive_data *ald_get(struct archive *ar, int no); static void ald_free_data(struct archive_data *data); diff --git a/src/meson.build b/src/meson.build index aa40788..5d44f3b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,6 @@ # sources for libsys4.a -system4 = ['ain.c', +system4 = ['afa.c', + 'ain.c', 'ald.c', 'archive.c', 'buffer.c', diff --git a/src/tools/ar/alice-ar.c b/src/tools/ar/alice-ar.c new file mode 100644 index 0000000..4b263df --- /dev/null +++ b/src/tools/ar/alice-ar.c @@ -0,0 +1,170 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include "system4.h" +#include "system4/afa.h" +#include "system4/string.h" +#include "system4/utfsjis.h" + +static void usage(void) +{ + puts("Usage: alice-ar [options...] input-file"); + puts(" Manipulate AliceSoft archive files (ald, afa)"); + puts(""); + puts(" -h, --help Display this message and exit"); + puts(" -x, --extract Extract archive"); + puts(" -c, --create Create an archive"); + puts(" -a, --add Add to archive"); + puts(" -d, --delete Delete a file from the archive"); + puts(" -o, --output Specify output file/directory"); + puts(" -i, --index Specify file index"); + puts(" -n, --name Specify file name"); + puts(" -f, --force Allow replacing existing files when using the --add option"); +} + +enum { + LOPT_HELP = 256, + LOPT_EXTRACT, + LOPT_CREATE, + LOPT_ADD, + LOPT_DELETE, + LOPT_LIST, + LOPT_OUTPUT, + LOPT_INDEX, + LOPT_NAME, + LOPT_FORCE, +}; + +int main(int argc, char *argv[]) +{ + char *output_file = NULL; + char *input_file = NULL; + char *file_name = NULL; + int file_index = -1; + + bool extract = false; + bool create = false; + bool add = false; + bool delete = false; + bool list = false; + bool force = false; + + while (1) { + static struct option long_options[] = { + { "help", no_argument, 0, LOPT_HELP }, + { "extract", no_argument, 0, LOPT_EXTRACT }, + { "create", required_argument, 0, LOPT_CREATE }, + { "add", required_argument, 0, LOPT_ADD }, + { "delete", no_argument, 0, LOPT_DELETE }, + { "list", no_argument, 0, LOPT_LIST }, + { "output", required_argument, 0, LOPT_OUTPUT }, + { "index", required_argument, 0, LOPT_INDEX }, + { "name", required_argument, 0, LOPT_NAME }, + { "force", no_argument, 0, LOPT_FORCE }, + }; + int option_index = 0; + int c = getopt_long(argc, argv, "hxc:a:dlo:i:n:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + case LOPT_HELP: + usage(); + return 0; + case 'x': + case LOPT_EXTRACT: + extract = true; + break; + case 'c': + case LOPT_CREATE: + create = true; + break; + case 'a': + case LOPT_ADD: + input_file = optarg; + break; + case 'd': + case LOPT_DELETE: + delete = true; + break; + case 'l': + case LOPT_LIST: + list = true; + break; + case 'o': + case LOPT_OUTPUT: + output_file = optarg; + break; + case 'i': + case LOPT_INDEX: + // FIXME: use strtol with error checking + file_index = atoi(optarg); + break; + case 'n': + file_name = optarg; + break; + case '?': + ERROR("Unrecognized command line argument"); + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(); + ERROR("Wrong number of arguments"); + } + + int nr_commands = extract + create + add + delete + list; + if (nr_commands > 1) + ERROR("Multiple commands given in single command line"); + if (!nr_commands) + ERROR("No command given"); + + if (create) { + WARNING("archive creation not yet implemented"); + return 0; + } + + int error; + struct afa_archive *ar = afa_open(argv[0], ARCHIVE_MMAP, &error); + if (!ar) { + ERROR("Opening archive: %s", archive_strerror(error)); + } + + if (extract) { + WARNING("archive extraction not yet implemented"); + } 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_free(&ar->ar); + return 0; +} diff --git a/src/tools/ar/meson.build b/src/tools/ar/meson.build new file mode 100644 index 0000000..6609b77 --- /dev/null +++ b/src/tools/ar/meson.build @@ -0,0 +1,8 @@ +alicear = ['alice-ar.c', +] + +executable('alice-ar', alicear, + dependencies: [libm, zlib], + c_args : ['-Wno-unused-parameter'], + include_directories : incdir, + link_with : libsys4) diff --git a/src/tools/meson.build b/src/tools/meson.build index b7d5048..78eebac 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -16,4 +16,5 @@ endif subdir('aindump') subdir('ainedit') +subdir('ar') subdir('ex')