From 19ac7e6935bdb8464ea133526946b2e64fcb9ecc Mon Sep 17 00:00:00 2001 From: Nunuhara Cabbage Date: Sun, 5 Jun 2022 19:01:54 -0700 Subject: [PATCH] Refactor asset_manager (again) There are now two types of asset management: 1. ID-indexed assets, where IDs are not necessarily sequential 2. name-indexed assets Name-indexed assets can have additional archives added at run-time. This is to support the CGManager.LoadArchive function used in Rance Quest Magnum. Name-indexed assets always use .afa archives. ID-indexed assets mostly use .ald archives, except for Daiteikoku and Shaman's Sanctuary. asset_get_by_name is now case insensitive and ignores the file extension. --- include/asset_manager.h | 7 +- include/xsystem4.h | 2 + src/asset_manager.c | 355 +++++++++++++++++++++++++++++-------- src/ffi.c | 2 + src/hacks.c | 4 + src/hll/CGManager.c | 54 ++++++ src/meson.build | 1 + src/parts/construction.c | 16 +- src/parts/parts_internal.h | 3 +- subprojects/libsys4 | 2 +- 10 files changed, 362 insertions(+), 84 deletions(-) create mode 100644 src/hll/CGManager.c diff --git a/include/asset_manager.h b/include/asset_manager.h index 1307733..b9a1a10 100644 --- a/include/asset_manager.h +++ b/include/asset_manager.h @@ -36,12 +36,15 @@ enum asset_type { const char *asset_strtype(enum asset_type type); void asset_manager_init(void); +bool asset_manager_load_archive(enum asset_type type, const char *archive_name); bool asset_exists(enum asset_type type, int no); +bool asset_exists_by_name(enum asset_type type, const char *name, int *id_out); struct archive_data *asset_get(enum asset_type type, int no); -int asset_name_to_index(enum asset_type type, const char *name); +struct archive_data *asset_get_by_name(enum asset_type type, const char *name, int *id_out); struct cg *asset_cg_load(int no); -struct cg *asset_cg_load_by_name(const char *name, int *no); +struct cg *asset_cg_load_by_name(const char *name, int *id_out); bool asset_cg_get_metrics(int no, struct cg_metrics *metrics); +bool asset_cg_get_metrics_by_name(const char *name, struct cg_metrics *metrics); #endif /* SYSTEM4_ASSET_MANAGER_H */ diff --git a/include/xsystem4.h b/include/xsystem4.h index 6cd521e..783864e 100644 --- a/include/xsystem4.h +++ b/include/xsystem4.h @@ -72,4 +72,6 @@ extern bool game_rance02_mg; extern bool game_rance6_mg; extern bool game_rance7_mg; +extern bool id_indexed_afa; + #endif /* XSYSTEM4_H */ diff --git a/src/asset_manager.c b/src/asset_manager.c index ea74713..4c94ca3 100644 --- a/src/asset_manager.c +++ b/src/asset_manager.c @@ -29,6 +29,7 @@ #include "system4/cg.h" #include "system4/file.h" #include "system4/hashtable.h" +#include "system4/string.h" #include "system4/utfsjis.h" #include "xsystem4.h" @@ -55,18 +56,274 @@ const char *asset_strtype(enum asset_type type) return "Invalid"; } -static struct archive *archives[ASSET_TYPE_MAX] = {0}; +#define MAX_ARCHIVES 8 + +struct asset_manager { + bool (*load_archive)(struct asset_manager *manager, const char *name); + bool (*exists_by_id)(struct asset_manager *manager, int id); + bool (*exists_by_name)(struct asset_manager *manager, const char *name, int *id_out); + struct archive_data *(*get_by_id)(struct asset_manager *manager, int id); + struct archive_data *(*get_by_name)(struct asset_manager *manager, const char *name, int *id_out); +}; + +// XXX: also used for AFAv1 in Daiteikoku, Shaman's Sanctuary +struct asset_manager_ald { + struct asset_manager manager; + struct archive *archive; +}; + +struct asset_manager_afa { + struct asset_manager manager; + struct afa_archive *archives[MAX_ARCHIVES]; + struct hash_table *index; +}; + +static struct asset_manager *assets[ASSET_TYPE_MAX] = {0}; + +bool asset_manager_load_archive(enum asset_type type, const char *archive_name) +{ + if (!assets[type]) + return false; + if (!assets[type]->load_archive) + ERROR("load_archive not supported on this archive type"); + return assets[type]->load_archive(assets[type], archive_name); +} + +bool asset_exists(enum asset_type type, int id) +{ + if (!assets[type]) + return false; + return assets[type]->exists_by_id(assets[type], id); +} + +bool asset_exists_by_name(enum asset_type type, const char *name, int *id_out) +{ + if (!assets[type]) + return false; + if (!assets[type]->exists_by_name) + ERROR("exists_by_name not supported on this archive type"); + return assets[type]->exists_by_name(assets[type], name, id_out); +} + +struct archive_data *asset_get(enum asset_type type, int id) +{ + if (!assets[type]) + return NULL; + return assets[type]->get_by_id(assets[type], id); +} + +struct archive_data *asset_get_by_name(enum asset_type type, const char *name, int *id_out) +{ + if (!assets[type]) + return NULL; + if (!assets[type]->get_by_name) + ERROR("get_by_name not supported on this archive type"); + return assets[type]->get_by_name(assets[type], name, id_out); +} + +struct cg *asset_cg_load(int id) +{ + struct archive_data *data = asset_get(ASSET_CG, id); + if (!data) + return NULL; + struct cg *cg = cg_load_data(data); + archive_free_data(data); + return cg; +} + +struct cg *asset_cg_load_by_name(const char *name, int *id_out) +{ + struct archive_data *data = asset_get_by_name(ASSET_CG, name, id_out); + if (!data) + return NULL; + struct cg *cg = cg_load_data(data); + archive_free_data(data); + return cg; +} + +bool asset_cg_get_metrics(int id, struct cg_metrics *metrics) +{ + struct archive_data *data = asset_get(ASSET_CG, id); + if (!data) + return false; + bool r = cg_get_metrics_data(data, metrics); + archive_free_data(data); + return r; +} + +bool asset_cg_get_metrics_by_name(const char *name, struct cg_metrics *metrics) +{ + struct archive_data *data = asset_get_by_name(ASSET_CG, name, NULL); + if (!data) + return false; + bool r = cg_get_metrics_data(data, metrics); + archive_free_data(data); + return r; +} + +static bool ald_exists_by_id(struct asset_manager *_manager, int id) +{ + struct asset_manager_ald *manager = (struct asset_manager_ald*)_manager; + return archive_exists(manager->archive, id - 1); +} + +static struct archive_data *ald_get_by_id(struct asset_manager *_manager, int id) +{ + struct asset_manager_ald *manager = (struct asset_manager_ald*)_manager; + return archive_get(manager->archive, id - 1); +} + +static bool afa_load_archive(struct asset_manager *_manager, const char *name) +{ + struct asset_manager_afa *manager = (struct asset_manager_afa*)_manager; + if (manager->archives[MAX_ARCHIVES-1]) + ERROR("Archive limit exceeded"); + + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "%s.afa", name); + + int error; + struct afa_archive *ar = afa_open(path, ARCHIVE_MMAP, &error); + if (!ar) { + snprintf(path, PATH_MAX, "%s.AFA", name); + ar = afa_open(path, ARCHIVE_MMAP, &error); + if (!ar) { + WARNING("Failed to open archive: %s", display_utf0(path)); + return false; + } + } + + for (int i = MAX_ARCHIVES - 1; i > 0; i++) { + manager->archives[i] = manager->archives[i-1]; + } + manager->archives[0] = ar; + + // index needs to be rebuilt + if (manager->index) { + ht_free(manager->index); + manager->index = NULL; + } + return true; +} + +static bool _afa_get_by_id(struct asset_manager *_manager, int id, struct afa_archive **ar_out, int *no_out) +{ + struct asset_manager_afa *manager = (struct asset_manager_afa*)_manager; + uint32_t no = id - 1; + for (int i = 0; i < MAX_ARCHIVES && manager->archives[i]; i++) { + if (no < manager->archives[i]->nr_files) { + *ar_out = manager->archives[i]; + *no_out = no; + return true; + } + no -= manager->archives[i]->nr_files; + } + return false; +} + +static bool afa_exists_by_id(struct asset_manager *manager, int id) +{ + int no; + struct afa_archive *ar; + return _afa_get_by_id(manager, id, &ar, &no); +} + +static struct archive_data *afa_get_by_id(struct asset_manager *manager, int id) +{ + int no; + struct afa_archive *ar; + if (!_afa_get_by_id(manager, id, &ar, &no)) + return NULL; + // XXX: we don't use archive_get here because it will return the wrong thing + // for AFAv1 archives when not using integer indices + return afa_entry_to_descriptor(ar, &ar->files[no]); +} + +static void afa_index_archive(struct hash_table *index, struct afa_archive *ar, unsigned base) +{ + for (unsigned i = 0; i < ar->nr_files; i++) { + // normalize case and remove file extension from file name + char *name = strdup(ar->files[i].name->text); + char *dot = strrchr(name, '.'); + if (dot) + *dot = '\0'; + sjis_toupper(name); + // add file to index + ht_put(index, name, (void*)((uintptr_t)base + i + 1)); + free(name); + } +} + +static bool _afa_get_by_name(struct asset_manager *_manager, const char *_name, + struct afa_archive **ar_out, int *no_out, int *id_out) +{ + struct asset_manager_afa *manager = (struct asset_manager_afa*)_manager; + // initialize index lazily + if (!manager->index) { + manager->index = ht_create(4096); + unsigned base = 0; + for (unsigned i = 0; i < MAX_ARCHIVES && manager->archives[i]; i++) { + afa_index_archive(manager->index, manager->archives[i], base); + base += manager->archives[i]->nr_files; + } + } + + // normalize name and get id from index + char *name = strdup(_name); + sjis_toupper(name); + uintptr_t id = (uintptr_t)ht_get(manager->index, name, NULL); + free(name); + + if (!id) + return false; + + if (id_out) + *id_out = id; + return _afa_get_by_id(_manager, id, ar_out, no_out); +} + +static bool afa_exists_by_name(struct asset_manager *manager, const char *name, int *id_out) +{ + int no; + struct afa_archive *ar; + return _afa_get_by_name(manager, name, &ar, &no, id_out); +} + +static struct archive_data *afa_get_by_name(struct asset_manager *manager, const char *name, int *id_out) +{ + int no; + struct afa_archive *ar; + if (!_afa_get_by_name(manager, name, &ar, &no, id_out)) + return NULL; + return archive_get(&ar->ar, no); +} + +static void _ald_init(enum asset_type type, struct archive *ar) +{ + struct asset_manager_ald *manager = xcalloc(1, sizeof(struct asset_manager_ald)); + manager->manager.exists_by_id = ald_exists_by_id; + manager->manager.get_by_id = ald_get_by_id; + manager->manager.load_archive = NULL; + manager->manager.exists_by_name = NULL; + manager->manager.get_by_name = NULL; + manager->archive = ar; + assets[type] = &manager->manager; +} static void ald_init(enum asset_type type, char **files, int count) { if (count < 1) return; - if (archives[type]) + if (assets[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) + + int error; + struct archive *ar = ald_open(files, count, ARCHIVE_MMAP, &error); + if (!ar) ERROR("Failed to open ALD file: %s", archive_strerror(error)); + + _ald_init(type, ar); + for (int i = 0; i < count; i++) { free(files[i]); } @@ -76,13 +333,27 @@ static void afa_init(enum asset_type type, char *file) { if (!file) return; - if (archives[type]) + if (assets[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) { + + int error; + struct afa_archive *ar = afa_open(file, ARCHIVE_MMAP, &error); + if (!ar) ERROR("Failed to open AFA file: %s", archive_strerror(error)); + + if (id_indexed_afa) { + _ald_init(type, &ar->ar); + } else { + struct asset_manager_afa *manager = xcalloc(1, sizeof(struct asset_manager_afa)); + manager->manager.load_archive = afa_load_archive; + manager->manager.exists_by_id = afa_exists_by_id; + manager->manager.exists_by_name = afa_exists_by_name; + manager->manager.get_by_id = afa_get_by_id; + manager->manager.get_by_name = afa_get_by_name; + manager->archives[0] = ar; + assets[type] = &manager->manager; } + free(file); } @@ -212,69 +483,3 @@ void asset_manager_init(void) afa_init(ASSET_PACT, afa_filenames[ASSET_PACT]); afa_init(ASSET_DATA, afa_filenames[ASSET_DATA]); } - -bool asset_exists(enum asset_type type, int no) -{ - return archives[type] && archive_exists(archives[type], no-1); -} - -struct archive_data *asset_get(enum asset_type type, int no) -{ - if (!archives[type]) - return NULL; - return archive_get(archives[type], no-1); -} - -struct archive_data *asset_get_by_name(enum asset_type type, const char *name) -{ - if (!archives[type]) - return NULL; - return archive_get_by_name(archives[type], name); -} - -int asset_name_to_index(enum asset_type type, const char *name) -{ - if (!archives[type]) - return 0; - // FIXME: should have archive_get_descriptor_by_name function to avoid loading data - struct archive_data *data = archive_get_by_name(archives[type], name); - if (!data) - return 0; - int index = data->no + 1; - archive_free_data(data); - return index; -} - -struct cg *asset_cg_load(int no) -{ - struct archive_data *data = asset_get(ASSET_CG, no); - if (!data) - return NULL; - - struct cg *cg = cg_load_data(data); - archive_free_data(data); - return cg; -} - -struct cg *asset_cg_load_by_name(const char *name, int *no) -{ - struct archive_data *data = asset_get_by_name(ASSET_CG, name); - if (!data) - return NULL; - - struct cg *cg = cg_load_data(data); - *no = data->no + 1; - archive_free_data(data); - return cg; -} - -bool asset_cg_get_metrics(int no, struct cg_metrics *metrics) -{ - struct archive_data *data = asset_get(ASSET_CG, no); - if (!data) - return false; - - bool r = cg_get_metrics_data(data, metrics); - archive_free_data(data); - return r; -} diff --git a/src/ffi.c b/src/ffi.c index fadb6fd..95137a9 100644 --- a/src/ffi.c +++ b/src/ffi.c @@ -298,6 +298,7 @@ extern struct static_library lib_AliceLogo5; extern struct static_library lib_AnteaterADVEngine; extern struct static_library lib_BanMisc; extern struct static_library lib_Bitarray; +extern struct static_library lib_CGManager; extern struct static_library lib_ChipmunkSpriteEngine; extern struct static_library lib_ChrLoader; extern struct static_library lib_CommonSystemData; @@ -363,6 +364,7 @@ static struct static_library *static_libraries[] = { &lib_AnteaterADVEngine, &lib_BanMisc, &lib_Bitarray, + &lib_CGManager, &lib_ChipmunkSpriteEngine, &lib_ChrLoader, &lib_CommonSystemData, diff --git a/src/hacks.c b/src/hacks.c index 9c21bf5..908568a 100644 --- a/src/hacks.c +++ b/src/hacks.c @@ -36,6 +36,8 @@ bool game_rance02_mg = false; bool game_rance6_mg = false; bool game_rance7_mg = false; +bool id_indexed_afa = false; + static void write_instruction0(struct buffer *out, enum opcode op) { buffer_write_int16(out, op); @@ -165,6 +167,8 @@ void apply_game_specific_hacks(struct ain *ain) apply_rance6_hacks(ain); } else if (!strcmp(game_name, "Sengoku Rance")) { apply_rance7_hacks(ain); + } else if (!strcmp(game_name, "しゃーまんず・さんくちゅあり 巫女の聖域")) { + id_indexed_afa = true; } free(game_name); } diff --git a/src/hll/CGManager.c b/src/hll/CGManager.c new file mode 100644 index 0000000..e1aa70d --- /dev/null +++ b/src/hll/CGManager.c @@ -0,0 +1,54 @@ +/* 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 "system4/string.h" +#include "system4/utfsjis.h" +#include "asset_manager.h" +#include "xsystem4.h" +#include "hll.h" + +static bool CGManager_Init(void *imain_system, int cg_cache_size) +{ + return true; +} + +static bool CGManager_LoadArchive(struct string *archive_name) +{ + char *name = sjis2utf(archive_name->text, 0); + bool r = asset_manager_load_archive(ASSET_CG, name); + free(name); + return r; +} + +//static int CGManager_GetCountOfDataFromArchive(void); +//static void CGManager_GetTitleByIndexFromArchive(int index, struct string **cg_name); +//static int CGManager_SearchTitleFromArchive(struct string *cg_name); +//static int CGManager_PrefixSearchTitleFromArchive(struct string *cg_name); +//static int CGManager_SuffixSearchTitleFromArchive(struct string *cg_name); +//static int CGManager_GetCountOfSearchDataFromArchive(void); +//static void CGManager_GetSearchTitleByIndexFromArchive(int index, struct string **cg_name); + +HLL_LIBRARY(CGManager, + HLL_EXPORT(Init, CGManager_Init), + HLL_EXPORT(LoadArchive, CGManager_LoadArchive), + HLL_TODO_EXPORT(GetCountOfDataFromArchive, CGManager_GetCountOfDataFromArchive), + HLL_TODO_EXPORT(GetTitleByIndexFromArchive, CGManager_GetTitleByIndexFromArchive), + HLL_TODO_EXPORT(SearchTitleFromArchive, CGManager_SearchTitleFromArchive), + HLL_TODO_EXPORT(PrefixSearchTitleFromArchive, CGManager_PrefixSearchTitleFromArchive), + HLL_TODO_EXPORT(SuffixSearchTitleFromArchive, CGManager_SuffixSearchTitleFromArchive), + HLL_TODO_EXPORT(GetCountOfSearchDataFromArchive, CGManager_GetCountOfSearchDataFromArchive), + HLL_TODO_EXPORT(GetSearchTitleByIndexFromArchive, CGManager_GetSearchTitleByIndexFromArchive)); + diff --git a/src/meson.build b/src/meson.build index c5c6ec1..bda891e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -62,6 +62,7 @@ xsystem4 = [version_h, 'hll/AnteaterADVEngine.c', 'hll/BanMisc.c', 'hll/Bitarray.c', + 'hll/CGManager.c', 'hll/CharSpriteManager.c', 'hll/ChipmunkSpriteEngine.c', 'hll/ChrLoader.c', diff --git a/src/parts/construction.c b/src/parts/construction.c index 3094a6b..f338e8c 100644 --- a/src/parts/construction.c +++ b/src/parts/construction.c @@ -25,6 +25,13 @@ void parts_cp_op_free(struct parts_cp_op *op) { + switch (op->type) { + case PARTS_CP_CG: + free_string(op->cg.name); + break; + case PARTS_CP_FILL_ALPHA_COLOR: + break; + } free(op); } @@ -43,15 +50,14 @@ static bool parts_add_cp_op(int parts_no, struct parts_cp_op *op, int state) bool PE_AddCreateCGToProcess(int parts_no, struct string *cg_name, int state) { - int cg_no = asset_name_to_index(ASSET_CG, cg_name->text); - if (!cg_no) { - WARNING("Invalid CG name: %s", cg_name); + if (!asset_exists_by_name(ASSET_CG, cg_name->text, NULL)) { + WARNING("Invalid CG name: %s", display_sjis0(cg_name->text)); return false; } struct parts_cp_op *op = xcalloc(1, sizeof(struct parts_cp_op)); op->type = PARTS_CP_CG; - op->cg.no = cg_no; + op->cg.name = string_dup(cg_name); return parts_add_cp_op(parts_no, op, state); } @@ -71,7 +77,7 @@ bool PE_AddFillAlphaColorToPartsConstructionProcess(int parts_no, int x, int y, static void build_cg(struct parts *parts, struct parts_cp_cg *op, int state) { // TODO: should this allow composing CGs? - parts_set_cg_by_index(parts, op->no, state); + parts_set_cg_by_name(parts, op->name, state); } static void build_fill_alpha_color(struct parts *parts, struct parts_cp_fill_alpha_color *op, int state) diff --git a/src/parts/parts_internal.h b/src/parts/parts_internal.h index cb61ef7..0d6cda3 100644 --- a/src/parts/parts_internal.h +++ b/src/parts/parts_internal.h @@ -126,7 +126,7 @@ enum parts_cp_op_type { }; struct parts_cp_cg { - int no; + struct string *name; }; struct parts_cp_fill_alpha_color { @@ -202,6 +202,7 @@ void parts_set_rotation_z(struct parts *parts, float rot); void parts_set_alpha(struct parts *parts, int alpha); void parts_set_cg_dims(struct parts *parts, struct cg *cg); bool parts_set_cg_by_index(struct parts *parts, int cg_no, int state); +bool parts_set_cg_by_name(struct parts *parts, struct string *cg_name, int state); void parts_set_hgauge_rate(struct parts *parts, float rate, int state); void parts_set_vgauge_rate(struct parts *parts, float rate, int state); bool parts_set_number(struct parts *parts, int n, int state); diff --git a/subprojects/libsys4 b/subprojects/libsys4 index 60e6e89..42a70d0 160000 --- a/subprojects/libsys4 +++ b/subprojects/libsys4 @@ -1 +1 @@ -Subproject commit 60e6e8981e2c15e9106d56d2d967b137daecaefa +Subproject commit 42a70d0b29736ca29f8284268b8776150619bbac