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