mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-10 16:28:21 +03:00
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:
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
@@ -54,6 +54,7 @@ enum {
|
||||
ALDFILETYPE_MAX
|
||||
};
|
||||
|
||||
// FIXME: this doesn't belong in a libsys4 header
|
||||
struct archive *ald[ALDFILETYPE_MAX];
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -1,5 +1,6 @@
|
||||
# sources for libsys4.a
|
||||
system4 = ['ain.c',
|
||||
system4 = ['afa.c',
|
||||
'ain.c',
|
||||
'ald.c',
|
||||
'archive.c',
|
||||
'buffer.c',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -16,4 +16,5 @@ endif
|
||||
|
||||
subdir('aindump')
|
||||
subdir('ainedit')
|
||||
subdir('ar')
|
||||
subdir('ex')
|
||||
|
||||
Reference in New Issue
Block a user