From f25e26ecf468e8fe5ea809159b1716ac342c15df Mon Sep 17 00:00:00 2001 From: Nunuhara Cabbage Date: Sat, 15 May 2021 18:56:49 -0700 Subject: [PATCH] Refactor archive management code Implement a new interface for managing archives. This is mainly to support loading afa archives alongside ald archives. Also included here is some code for creating an index for afa CG archives (the numbers used to reference CGs in afa archives correspond to the file name, not the position in the archive). --- include/asset_manager.h | 48 ++++++ include/xsystem4.h | 4 +- src/asset_manager.c | 290 ++++++++++++++++++++++++++++++++++++ src/audio.c | 11 +- src/hll/DataFile.c | 5 +- src/hll/Gpx2Plus.c | 4 +- src/hll/SACT2.c | 6 +- src/hll/StoatSpriteEngine.c | 1 - src/meson.build | 1 + src/sprite.c | 6 +- src/system4.c | 91 +---------- subprojects/libsys4 | 2 +- 12 files changed, 361 insertions(+), 108 deletions(-) create mode 100644 include/asset_manager.h create mode 100644 src/asset_manager.c diff --git a/include/asset_manager.h b/include/asset_manager.h new file mode 100644 index 0000000..9ceb0a7 --- /dev/null +++ b/include/asset_manager.h @@ -0,0 +1,48 @@ +/* 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_ASSET_MANAGER_H +#define SYSTEM4_ASSET_MANAGER_H + +#include + +struct archive_data; +struct cg; +struct cg_metrics; + +enum asset_type { + ASSET_BGM, + ASSET_SOUND, + ASSET_VOICE, + ASSET_CG, + ASSET_FLAT, + ASSET_PACT, + ASSET_DATA +}; +#define ASSET_TYPE_MAX (ASSET_DATA+1) + +const char *asset_strtype(enum asset_type type); +void asset_manager_init(void); +bool asset_exists(enum asset_type type, int no); +struct archive_data *asset_get(enum asset_type type, int no); + +// FIXME: CG code should be refactored to read from buffer only, not archive, +// then caller can just use asset_get and cg_load +struct cg *asset_cg_load(int no); +bool asset_cg_get_metrics(int no, struct cg_metrics *metrics); +void asset_cg_index_init(void); + +#endif /* SYSTEM4_ASSET_MANAGER_H */ diff --git a/include/xsystem4.h b/include/xsystem4.h index 32dbefb..ab8199f 100644 --- a/include/xsystem4.h +++ b/include/xsystem4.h @@ -17,9 +17,7 @@ #ifndef XSYSTEM4_H #define XSYSTEM4_H -#include "system4/ald.h" - -extern struct archive *ald[ALDFILETYPE_MAX]; +#include struct config { char *game_name; diff --git a/src/asset_manager.c b/src/asset_manager.c new file mode 100644 index 0000000..6c53bf5 --- /dev/null +++ b/src/asset_manager.c @@ -0,0 +1,290 @@ +/* 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 + +#include "system4.h" +#include "system4/ald.h" +#include "system4/afa.h" +#include "system4/cg.h" +#include "system4/file.h" +#include "system4/hashtable.h" +#include "system4/utfsjis.h" + +#include "xsystem4.h" +#include "asset_manager.h" + +enum archive_type { + AR_TYPE_ALD, + AR_TYPE_AFA, + AR_TYPE_MAX +}; + +const char *asset_strtype(enum asset_type type) +{ + switch (type) { + case ASSET_BGM: return "BGM"; + case ASSET_SOUND: return "Sound"; + case ASSET_VOICE: return "Voice"; + case ASSET_CG: return "CG"; + case ASSET_FLAT: return "Flat"; + case ASSET_PACT: return "Pact"; + case ASSET_DATA: return "Data"; + } + return "Invalid"; +} + +static struct archive *archives[ASSET_TYPE_MAX] = {0}; + +static void ald_init(enum asset_type type, char **files, int count) +{ + if (count < 1) + return; + if (archives[type]) + WARNING("Multiple asset archives for type %s", asset_strtype(type)); + int error = ARCHIVE_SUCCESS; + archives[type] = ald_open(files, count, ARCHIVE_MMAP, &error); + if (error) + ERROR("Failed to open ALD file: %s", archive_strerror(error)); + for (int i = 0; i < count; i++) { + free(files[i]); + } +} + +static void afa_init(enum asset_type type, char *file) +{ + if (!file) + return; + if (archives[type]) + WARNING("Multiple asset archives for type %s", asset_strtype(type)); + int error = ARCHIVE_SUCCESS; + archives[type] = (struct archive*)afa_open(file, ARCHIVE_MMAP, &error); + if (error != ARCHIVE_SUCCESS) { + ERROR("Failed to open AFA file: %s", archive_strerror(error)); + } + free(file); +} + +static char *get_base_name(const char *ain_filename) +{ + char *path = sjis2utf(ain_filename, 0); + char *dot = strrchr(path, '.'); + if (dot) + *dot = '\0'; + return path; +} + +void asset_manager_init(void) +{ + char *ald_filenames[ASSET_TYPE_MAX][ALD_FILEMAX] = {0}; + int ald_count[ASSET_TYPE_MAX] = {0}; + char *afa_filenames[ASSET_TYPE_MAX] = {0}; + + DIR *dir; + struct dirent *d; + + if (!(dir = opendir(config.game_dir))) { + ERROR("Failed to open directory: %s", config.game_dir); + } + + char *base = get_base_name(config.ain_filename); + size_t base_len = strlen(base); + + // get ALD filenames + while ((d = readdir(dir))) { + // archive filename must begin with ain filename + if (strncmp(base, d->d_name, base_len)) + continue; + const char *ext = file_extension(d->d_name); + if (!ext) + continue; + if (!strcasecmp(ext, "ald")) { + int dno = toupper(d->d_name[base_len+1]) - 'A'; + if (dno < 0 || dno >= ALD_FILEMAX) { + WARNING("Invalid ALD index: %s", d->d_name); + continue; + } + + switch (d->d_name[base_len]) { + case 'b': + case 'B': + ald_filenames[ASSET_BGM][dno] = path_join(config.game_dir, d->d_name); + ald_count[ASSET_BGM] = max(ald_count[ASSET_BGM], dno+1); + break; + case 'g': + case 'G': + ald_filenames[ASSET_CG][dno] = path_join(config.game_dir, d->d_name); + ald_count[ASSET_CG] = max(ald_count[ASSET_CG], dno+1); + break; + case 'w': + case 'W': + ald_filenames[ASSET_SOUND][dno] = path_join(config.game_dir, d->d_name); + ald_count[ASSET_SOUND] = max(ald_count[ASSET_SOUND], dno+1); + break; + case 'd': + case 'D': + ald_filenames[ASSET_DATA][dno] = path_join(config.game_dir, d->d_name); + ald_count[ASSET_DATA] = max(ald_count[ASSET_DATA], dno+1); + break; + default: + WARNING("Unhandled ALD file: %s", d->d_name); + break; + } + } else if (!strcasecmp(ext, "bgi")) { + if (config.bgi_path) { + WARNING("Multiple bgi files"); + continue; + } + config.bgi_path = path_join(config.game_dir, d->d_name); + } else if (!strcasecmp(ext, "wai")) { + if (config.wai_path) { + WARNING("Multiple wai files"); + continue; + } + config.wai_path = path_join(config.game_dir, d->d_name); + } else if (!strcasecmp(ext, "afa")) { + const char *type = d->d_name + base_len; + if (!strcmp(type, "BA.afa")) { + afa_filenames[ASSET_BGM] = path_join(config.game_dir, d->d_name); + } else if (!strcmp(type, "WA.afa") || !strcmp(type, "Sound.afa")) { + afa_filenames[ASSET_SOUND] = path_join(config.game_dir, d->d_name); + } else if (!strcmp(type, "Voice.afa")) { + afa_filenames[ASSET_VOICE] = path_join(config.game_dir, d->d_name); + } else if (!strcmp(type, "CG.afa")) { + afa_filenames[ASSET_CG] = path_join(config.game_dir, d->d_name); + } else if (!strcmp(type, "Flat.afa")) { + afa_filenames[ASSET_FLAT] = path_join(config.game_dir, d->d_name); + } else if (!strcmp(type, "Pact.afa")) { + afa_filenames[ASSET_PACT] = path_join(config.game_dir, d->d_name); + } + } + } + + NOTICE("%d CG archives", ald_count[ASSET_CG]); + + // open ALD archives + ald_init(ASSET_BGM, ald_filenames[ASSET_BGM], ald_count[ASSET_BGM]); + ald_init(ASSET_SOUND, ald_filenames[ASSET_SOUND], ald_count[ASSET_SOUND]); + ald_init(ASSET_VOICE, ald_filenames[ASSET_VOICE], ald_count[ASSET_VOICE]); + ald_init(ASSET_CG, ald_filenames[ASSET_CG], ald_count[ASSET_CG]); + ald_init(ASSET_FLAT, ald_filenames[ASSET_FLAT], ald_count[ASSET_FLAT]); + ald_init(ASSET_PACT, ald_filenames[ASSET_PACT], ald_count[ASSET_PACT]); + ald_init(ASSET_DATA, ald_filenames[ASSET_DATA], ald_count[ASSET_DATA]); + + // open AFA archives + afa_init(ASSET_BGM, afa_filenames[ASSET_BGM]); + afa_init(ASSET_SOUND, afa_filenames[ASSET_SOUND]); + afa_init(ASSET_VOICE, afa_filenames[ASSET_VOICE]); + afa_init(ASSET_CG, afa_filenames[ASSET_CG]); + afa_init(ASSET_FLAT, afa_filenames[ASSET_FLAT]); + afa_init(ASSET_PACT, afa_filenames[ASSET_PACT]); + afa_init(ASSET_DATA, afa_filenames[ASSET_DATA]); + + free(base); + closedir(dir); +} + +bool asset_exists(enum asset_type type, int no) +{ + return archives[type] && archive_exists(archives[type], no); +} + +struct archive_data *asset_get(enum asset_type type, int no) +{ + if (!archives[type]) + return NULL; + return archive_get(archives[type], no); +} + +static struct hash_table *cg_index = NULL; + +// Convert the last sequence of digits in a string to an integer. +static int cg_name_to_int(const char *name) +{ + size_t len = strlen(name); + int i; + for (i = len - 1; i >= 0 && !isdigit(name[i]); i--); + if (i < 0) + return -1; + assert(isdigit(name[i])); + for (; i > 0 && isdigit(name[i-1]); i--); + assert(isdigit(name[i])); + + if (!(i = atoi(name+i))) + return -1; + return i; +} + +static void add_cg_to_index(struct archive_data *data, possibly_unused void *_) +{ + int logical_no = cg_name_to_int(data->name); + if (logical_no < 0) { + WARNING("Can't determine logical index for CG: %s", data->name); + return; + } + + struct ht_slot *slot = ht_put_int(cg_index, logical_no, NULL); + if (slot->value) + ERROR("Duplicate CG numbers"); + slot->value = (void*)(uintptr_t)(data->no + 1); +} + +/* + * NOTE: Starting in Shaman's Sanctuary, .afa files are used for CGs but the + * library APIs still use integers rather than names to reference them. + * Thus it is necessary to parse file names and create an index. + */ +void asset_cg_index_init(void) +{ + if (!archives[ASSET_CG]) + return; + + cg_index = ht_create(4096); + archive_for_each(archives[ASSET_CG], add_cg_to_index, NULL); +} + +static int cg_translate_index(int no) +{ + if (!cg_index) + return no; + return (uintptr_t)ht_get_int(cg_index, no, NULL); +} + +struct cg *asset_cg_load(int no) +{ + if (!archives[ASSET_CG]) + return NULL; + if (!(no = cg_translate_index(no))) + return NULL; + return cg_load(archives[ASSET_CG], no - 1); +} + +bool asset_cg_get_metrics(int no, struct cg_metrics *metrics) +{ + if (!archives[ASSET_CG]) + return false; + if (!(no = cg_translate_index(no))) + return NULL; + return cg_get_metrics(archives[ASSET_CG], no - 1, metrics); +} + diff --git a/src/audio.c b/src/audio.c index fd936bb..9f9bd78 100644 --- a/src/audio.c +++ b/src/audio.c @@ -17,9 +17,10 @@ #include #include "system4.h" -#include "system4/ald.h" +#include "system4/archive.h" #include "system4/file.h" +#include "asset_manager.h" #include "audio.h" #include "xsystem4.h" @@ -207,17 +208,17 @@ static struct bgm_slot *bgm_get_slot(int ch) bool wav_exists(int no) { - return ald[ALDFILE_WAVE] && archive_exists(ald[ALDFILE_WAVE], no); + return asset_exists(ASSET_SOUND, no); } bool bgm_exists(int no) { - return ald[ALDFILE_BGM] && archive_exists(ald[ALDFILE_BGM], no); + return asset_exists(ASSET_BGM, no); } static bool wav_load_chunk(int no, struct wav_slot *slot) { - struct archive_data *dfile = archive_get(ald[ALDFILE_WAVE], no); + struct archive_data *dfile = asset_get(ASSET_SOUND, no); if (!dfile) { WARNING("Failed to load WAV %d", no); return false; @@ -237,7 +238,7 @@ static bool wav_load_chunk(int no, struct wav_slot *slot) static bool bgm_load_music(int no, struct bgm_slot *slot) { - struct archive_data *dfile = archive_get(ald[ALDFILE_BGM], no); + struct archive_data *dfile = asset_get(ASSET_BGM, no); if (!dfile) { WARNING("Failed to load WAV %d", no); return false; diff --git a/src/hll/DataFile.c b/src/hll/DataFile.c index 8db8afc..13f406b 100644 --- a/src/hll/DataFile.c +++ b/src/hll/DataFile.c @@ -15,13 +15,16 @@ */ #include +#include #include +#include "system4/archive.h" #include "system4/buffer.h" #include "system4/mt19937int.h" #include "system4/string.h" #include "hll.h" +#include "asset_manager.h" #include "xsystem4.h" enum value_type { @@ -248,7 +251,7 @@ possibly_unused static void gdat_dump_as_json(struct gdat *dat, FILE *fp) static int DataFile_Open(int link) { - struct archive_data *dfile = archive_get(ald[ALDFILE_DATA], link - 1); + struct archive_data *dfile = asset_get(ASSET_DATA, link - 1); if (!dfile) { WARNING("Failed to load data file %d", link); return 0; diff --git a/src/hll/Gpx2Plus.c b/src/hll/Gpx2Plus.c index 24ac36e..2deaf6f 100644 --- a/src/hll/Gpx2Plus.c +++ b/src/hll/Gpx2Plus.c @@ -17,12 +17,12 @@ #include #include "system4.h" -#include "system4/ald.h" #include "system4/cg.h" #include "system4/string.h" #include "system4/utfsjis.h" #include "hll.h" +#include "asset_manager.h" #include "audio.h" #include "effect.h" #include "gfx/gfx.h" @@ -175,7 +175,7 @@ static int Gpx2Plus_LoadCG(int cg_num) { if (!cg_num) return -1; - struct cg *cg = cg_load(ald[ALDFILE_CG], cg_num - 1); + struct cg *cg = asset_cg_load(cg_num); if (!cg) return -1; struct gpx_surface *sf = create_surface(1, 1); diff --git a/src/hll/SACT2.c b/src/hll/SACT2.c index 7f7d92b..ab4f911 100644 --- a/src/hll/SACT2.c +++ b/src/hll/SACT2.c @@ -19,11 +19,11 @@ #include "system4.h" #include "system4/ain.h" -#include "system4/ald.h" #include "system4/cg.h" #include "system4/string.h" #include "hll.h" +#include "asset_manager.h" #include "audio.h" #include "input.h" #include "queue.h" @@ -559,7 +559,7 @@ int sact_CG_GetMetrics(int cg_no, struct page **page) { union vm_value *cgm = (*page)->values; struct cg_metrics metrics; - if (!cg_get_metrics(ald[ALDFILE_CG], cg_no - 1, &metrics)) + if (!asset_cg_get_metrics(cg_no, &metrics)) return 0; cgm[0].i = metrics.w; cgm[1].i = metrics.h; @@ -678,7 +678,7 @@ int sact_Key_IsDown(int keycode) int sact_CG_IsExist(int cg_no) { - return ald[ALDFILE_CG] && archive_exists(ald[ALDFILE_CG], cg_no - 1); + return asset_exists(ASSET_CG, cg_no - 1); } //int SACT2_CSV_Load(struct string *filename); diff --git a/src/hll/StoatSpriteEngine.c b/src/hll/StoatSpriteEngine.c index e7691da..653285d 100644 --- a/src/hll/StoatSpriteEngine.c +++ b/src/hll/StoatSpriteEngine.c @@ -20,7 +20,6 @@ #include "system4.h" #include "system4/ain.h" -#include "system4/ald.h" #include "system4/cg.h" #include "system4/string.h" #include "system4/utfsjis.h" diff --git a/src/meson.build b/src/meson.build index 3c2dfcc..5896054 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,6 @@ # sources for xsystem4 xsystem4 = ['audio.c', + 'asset_manager.c', 'cJSON.c', 'draw.c', 'effect.c', diff --git a/src/sprite.c b/src/sprite.c index 0a4a489..b6f94f3 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -17,11 +17,11 @@ #include #include "system4.h" -#include "system4/ald.h" #include "system4/cg.h" #include "system4/string.h" #include "system4/utfsjis.h" +#include "asset_manager.h" #include "audio.h" #include "input.h" #include "gfx/gfx.h" @@ -118,7 +118,7 @@ int sprite_set_wp(int cg_no) return 1; } - struct cg *cg = cg_load(ald[ALDFILE_CG], cg_no - 1); + struct cg *cg = asset_cg_load(cg_no); if (!cg) return 0; @@ -139,7 +139,7 @@ int sprite_set_wp_color(int r, int g, int b) int sprite_set_cg(struct sact_sprite *sp, int cg_no) { - struct cg *cg = cg_load(ald[ALDFILE_CG], cg_no - 1); + struct cg *cg = asset_cg_load(cg_no); if (!cg) return 0; gfx_init_texture_with_cg(&sp->texture, cg); diff --git a/src/system4.c b/src/system4.c index c585e5e..d68e807 100644 --- a/src/system4.c +++ b/src/system4.c @@ -26,7 +26,6 @@ #include "system4.h" #include "system4/ain.h" -#include "system4/ald.h" #include "system4/file.h" #include "system4/ini.h" #include "system4/instructions.h" @@ -34,6 +33,7 @@ #include "system4/utfsjis.h" #include "xsystem4.h" +#include "asset_manager.h" #include "debugger.h" #include "file.h" #include "gfx/gfx.h" @@ -185,93 +185,6 @@ static void read_user_config(void) free(path); } -struct archive *ald[ALDFILETYPE_MAX]; -static char *ald_filenames[ALDFILETYPE_MAX][ALD_FILEMAX]; -static int ald_count[ALDFILETYPE_MAX]; - -void ald_init(int type, char **files, int count) -{ - int error = ARCHIVE_SUCCESS; - ald[type] = ald_open(files, count, ARCHIVE_MMAP, &error); - if (error) - ERROR("Failed to open ALD file: %s\n", archive_strerror(error)); -} - -static void init_gamedata_dir(const char *path) -{ - DIR *dir; - struct dirent *d; - char filepath[512] = { [511] = '\0' }; - - if (!(dir = opendir(path))) { - ERROR("Failed to open directory: %s", path); - } - - // get ALD filenames - while ((d = readdir(dir))) { - int dno; - size_t len = strlen(d->d_name); - snprintf(filepath, 511, "%s/%s", path, d->d_name); - if (!strcasecmp(d->d_name+len-4, ".ald")) { - dno = toupper(*(d->d_name+len-5)) - 'A'; - if (dno < 0 || dno >= ALD_FILEMAX) { - WARNING("Invalid ALD index: %s", d->d_name); - continue; - } - - switch (*(d->d_name+len-6)) { - case 'b': - case 'B': - ald_filenames[ALDFILE_BGM][dno] = strdup(filepath); - ald_count[ALDFILE_BGM] = max(ald_count[ALDFILE_BGM], dno+1); - break; - case 'g': - case 'G': - ald_filenames[ALDFILE_CG][dno] = strdup(filepath); - ald_count[ALDFILE_CG] = max(ald_count[ALDFILE_CG], dno+1); - break; - case 'w': - case 'W': - ald_filenames[ALDFILE_WAVE][dno] = strdup(filepath); - ald_count[ALDFILE_WAVE] = max(ald_count[ALDFILE_WAVE], dno+1); - break; - case 'd': - case 'D': - ald_filenames[ALDFILE_DATA][dno] = strdup(filepath); - ald_count[ALDFILE_DATA] = max(ald_count[ALDFILE_DATA], dno+1); - break; - default: - WARNING("Unhandled ALD file: %s", d->d_name); - break; - } - } else if (!strcasecmp(d->d_name+len-4, ".bgi")) { - if (config.bgi_path) { - WARNING("Multiple bgi files"); - continue; - } - config.bgi_path = strdup(filepath); - } else if (!strcasecmp(d->d_name+len-4, ".wai")) { - if (config.wai_path) { - WARNING("Multiple wai files"); - continue; - } - config.wai_path = strdup(filepath); - } - } - - // open ALD archives - if (ald_count[ALDFILE_BGM] > 0) - ald_init(ALDFILE_BGM, ald_filenames[ALDFILE_BGM], ald_count[ALDFILE_BGM]); - if (ald_count[ALDFILE_CG] > 0) - ald_init(ALDFILE_CG, ald_filenames[ALDFILE_CG], ald_count[ALDFILE_CG]); - if (ald_count[ALDFILE_WAVE] > 0) - ald_init(ALDFILE_WAVE, ald_filenames[ALDFILE_WAVE], ald_count[ALDFILE_WAVE]); - if (ald_count[ALDFILE_DATA] > 0) - ald_init(ALDFILE_DATA, ald_filenames[ALDFILE_DATA], ald_count[ALDFILE_DATA]); - - closedir(dir); -} - static char *get_xsystem4_home(void) { // $XSYSTEM4_HOME @@ -536,7 +449,7 @@ int main(int argc, char *argv[]) return 0; } - init_gamedata_dir(config.game_dir); + asset_manager_init(); #ifdef DEBUGGER_ENABLED dbg_init(); diff --git a/subprojects/libsys4 b/subprojects/libsys4 index 53943c0..7b6fd0b 160000 --- a/subprojects/libsys4 +++ b/subprojects/libsys4 @@ -1 +1 @@ -Subproject commit 53943c00ca967408729444f680ae3c366020825e +Subproject commit 7b6fd0b3ba0d789b3928f8db4b8f0a6f9801b98e