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).
This commit is contained in:
Nunuhara Cabbage
2021-05-16 08:06:40 -07:00
parent 92b81f21ee
commit f25e26ecf4
12 changed files with 361 additions and 108 deletions
+48
View File
@@ -0,0 +1,48 @@
/* 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_ASSET_MANAGER_H
#define SYSTEM4_ASSET_MANAGER_H
#include <stdbool.h>
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 */
+1 -3
View File
@@ -17,9 +17,7 @@
#ifndef XSYSTEM4_H
#define XSYSTEM4_H
#include "system4/ald.h"
extern struct archive *ald[ALDFILETYPE_MAX];
#include <stddef.h>
struct config {
char *game_name;
+290
View File
@@ -0,0 +1,290 @@
/* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <libgen.h>
#include <dirent.h>
#include <limits.h>
#include <assert.h>
#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);
}
+6 -5
View File
@@ -17,9 +17,10 @@
#include <SDL_mixer.h>
#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;
+4 -1
View File
@@ -15,13 +15,16 @@
*/
#include <string.h>
#include <stdio.h>
#include <zlib.h>
#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;
+2 -2
View File
@@ -17,12 +17,12 @@
#include <assert.h>
#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);
+3 -3
View File
@@ -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);
-1
View File
@@ -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"
+1
View File
@@ -1,5 +1,6 @@
# sources for xsystem4
xsystem4 = ['audio.c',
'asset_manager.c',
'cJSON.c',
'draw.c',
'effect.c',
+3 -3
View File
@@ -17,11 +17,11 @@
#include <stdlib.h>
#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);
+2 -89
View File
@@ -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();