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.
This commit is contained in:
Nunuhara Cabbage
2022-06-05 19:15:41 -07:00
parent 7eb620aca7
commit 19ac7e6935
10 changed files with 362 additions and 84 deletions
+5 -2
View File
@@ -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 */
+2
View File
@@ -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 */
+280 -75
View File
@@ -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;
}
+2
View File
@@ -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,
+4
View File
@@ -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);
}
+54
View File
@@ -0,0 +1,54 @@
/* 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 "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));
+1
View File
@@ -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',
+11 -5
View File
@@ -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)
+2 -1
View File
@@ -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);