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.
This commit is contained in:
Nunuhara Cabbage
2020-02-25 20:34:51 -08:00
parent be1b5ecc8f
commit 40b67ed7f6
9 changed files with 466 additions and 9 deletions
+8
View File
@@ -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 <sys/mman.h>
#endif
#endif /* SYSTEM4_H */
+53
View File
@@ -0,0 +1,53 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* 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 <http://gnu.org/licenses/>.
*/
#ifndef SYSTEM4_AFA_H
#define SYSTEM4_AFA_H
#include <stdint.h>
#include <stddef.h>
#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 */
+1
View File
@@ -54,6 +54,7 @@ enum {
ALDFILETYPE_MAX
};
// FIXME: this doesn't belong in a libsys4 header
struct archive *ald[ALDFILETYPE_MAX];
/*
+223
View File
@@ -0,0 +1,223 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* 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 <http://gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <zlib.h>
#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;
}
-8
View File
@@ -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 <sys/mman.h>
#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);
+2 -1
View File
@@ -1,5 +1,6 @@
# sources for libsys4.a
system4 = ['ain.c',
system4 = ['afa.c',
'ain.c',
'ald.c',
'archive.c',
'buffer.c',
+170
View File
@@ -0,0 +1,170 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* 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 <http://gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#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 <manifest> Create an archive");
puts(" -a, --add <file> Add <file> to archive");
puts(" -d, --delete Delete a file from the archive");
puts(" -o, --output Specify output file/directory");
puts(" -i, --index <index> Specify file index");
puts(" -n, --name <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;
}
+8
View File
@@ -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)
+1
View File
@@ -16,4 +16,5 @@ endif
subdir('aindump')
subdir('ainedit')
subdir('ar')
subdir('ex')