taisei/src/stageinfo.c
Andrei Alexeyev fda8556a39
src: deprecate strdup(), use mem_strdup() explicitly
This fixes a nasty bug that manifests on windows when building without
precompiled headers. util/stringops.h used to silently replace strdup
with a macro that's compatible with mem_free(). This header would
typically be included everywhere due to PCH, but without it the strdup
from libc would sometimes be in scope. On most platforms mem_free() is
equivalent to free(), but not on windows, because we have to use
_aligned_free() there. Attempting to mem_free() the result of a libc
strdup() would segfault in such a configuration.

Avoid the footgun by banning strdup() entirely. Maybe redefining libc
names isn't such a great idea, who knew?
2024-09-10 14:31:55 +02:00

252 lines
7.2 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "stageinfo.h"
#include "stages/stages.h"
#include "dynstage.h"
static struct {
DYNAMIC_ARRAY(StageInfo) stages;
StageProgress **stages_progress;
} stageinfo;
static void add_stage(
uint16_t id,
StageProcs *procs,
StageType type,
const char *title,
const char *subtitle,
AttackInfo *spell,
Difficulty diff
) {
dynarray_append(&stageinfo.stages, {
.id = id,
.procs = procs,
.type = type,
.spell = spell,
.difficulty = diff,
.title = title ? mem_strdup(title) : NULL,
.subtitle = subtitle ? mem_strdup(subtitle) : NULL,
});
}
static void add_spellpractice_stage(
StageInfo *s,
AttackInfo *a,
int *spellnum,
uint16_t spellbits,
Difficulty diff
) {
uint16_t id = spellbits | a->idmap[diff - D_Easy] | (s->id << 8);
char *title = strfmt("Spell %d", ++(*spellnum));
char *subtitle = strjoin(a->name, " ~ ", difficulty_name(diff), NULL);
add_stage(id, s->procs->spellpractice_procs, STAGE_SPELL, title, subtitle, a, diff);
mem_free(title);
mem_free(subtitle);
}
static void add_spellpractice_stages(
int *spellnum,
bool (*filter)(AttackInfo*),
uint16_t spellbits
) {
for(int i = 0 ;; ++i) {
StageInfo *s = dynarray_get_ptr(&stageinfo.stages, i);
if(s->type == STAGE_SPELL || !s->spell) {
break;
}
for(AttackInfo *a = s->spell; a->name; ++a) {
if(!filter(a)) {
continue;
}
for(Difficulty diff = D_Easy; diff < D_Easy + NUM_SELECTABLE_DIFFICULTIES; ++diff) {
if(a->idmap[diff - D_Easy] >= 0) {
add_spellpractice_stage(s, a, spellnum, spellbits, diff);
// stages may have gotten realloc'd, so we must update the pointer
s = dynarray_get_ptr(&stageinfo.stages, i);
}
}
}
}
}
static bool spellfilter_normal(AttackInfo *spell) {
return spell->type != AT_ExtraSpell;
}
static bool spellfilter_extra(AttackInfo *spell) {
return spell->type == AT_ExtraSpell;
}
static void stageinfo_fill(StagesExports *e);
void stageinfo_init(void) {
dynstage_init();
stageinfo_fill(dynstage_get_exports());
stageinfo.stages_progress = ALLOC_ARRAY(
stageinfo.stages.num_elements, typeof(*stageinfo.stages_progress));
}
void stageinfo_reload(void) {
if(!dynstage_reload_library()) {
log_debug("dynstage not reloaded");
return;
}
attr_unused StageInfo *prev_addr = stageinfo.stages.data;
attr_unused uint prev_count = stageinfo.stages.num_elements;
dynarray_foreach_elem(&stageinfo.stages, StageInfo *stg, {
mem_free(stg->title);
mem_free(stg->subtitle);
});
stageinfo.stages.num_elements = 0;
stageinfo_fill(dynstage_get_exports());
assert(stageinfo.stages.data == prev_addr);
assert(stageinfo.stages.num_elements == prev_count);
log_debug("Stageinfo updated after dynstage reload");
}
static void stageinfo_fill(StagesExports *e) {
int spellnum = 0;
// id procs type title subtitle spells diff
add_stage(1, e->stage1.procs, STAGE_STORY, "Stage 1", "Misty Lake Encounter", e->stage1.spells, D_Any);
add_stage(2, e->stage2.procs, STAGE_STORY, "Stage 2", "Riverside Hina-Nagashi", e->stage2.spells, D_Any);
add_stage(3, e->stage3.procs, STAGE_STORY, "Stage 3", "Crawly Mountain Ascent", e->stage3.spells, D_Any);
add_stage(4, e->stage4.procs, STAGE_STORY, "Stage 4", "Forgotten Mansion", e->stage4.spells, D_Any);
add_stage(5, e->stage5.procs, STAGE_STORY, "Stage 5", "Climbing the Tower of Babel", e->stage5.spells, D_Any);
add_stage(6, e->stage6.procs, STAGE_STORY, "Stage 6", "Roof of the World", e->stage6.spells, D_Any);
add_stage(7, e->stagex.procs, STAGE_SPECIAL, "Extra Stage", "Descent into Madness", e->stagex.spells, D_Extra);
#ifdef TAISEI_BUILDCONF_TESTING_STAGES
add_stage(0x40|0, e->testing.dps_single, STAGE_SPECIAL, "DPS Test", "Single target", NULL, D_Normal);
add_stage(0x40|1, e->testing.dps_multi, STAGE_SPECIAL, "DPS Test", "Multiple targets", NULL, D_Normal);
add_stage(0x40|2, e->testing.dps_boss, STAGE_SPECIAL, "DPS Test", "Boss", NULL, D_Normal);
#endif
// generate spellpractice stages
add_spellpractice_stages(&spellnum, spellfilter_normal, STAGE_SPELL_BIT);
add_spellpractice_stages(&spellnum, spellfilter_extra, STAGE_SPELL_BIT | STAGE_EXTRASPELL_BIT);
#ifdef TAISEI_BUILDCONF_TESTING_STAGES
add_spellpractice_stage(
dynarray_get_ptr(&stageinfo.stages, 0),
e->testing.benchmark_spell, &spellnum, STAGE_SPELL_BIT, D_Extra
);
#endif
dynarray_compact(&stageinfo.stages);
#ifdef DEBUG
dynarray_foreach(&stageinfo.stages, int i, StageInfo *stg, {
if(stg->type == STAGE_SPELL && !(stg->id & STAGE_SPELL_BIT)) {
log_fatal("Spell stage has an ID without the spell bit set: 0x%04x", stg->id);
}
dynarray_foreach(&stageinfo.stages, int j, StageInfo *stg2, {
if(stg != stg2 && stg->id == stg2->id) {
log_fatal("Duplicate ID %X in stages array, indices: %i, %i", stg->id, i, j);
}
});
});
#endif
}
void stageinfo_shutdown(void) {
dynarray_foreach(&stageinfo.stages, int i, StageInfo *stg, {
mem_free(stg->title);
mem_free(stg->subtitle);
mem_free(stageinfo.stages_progress[i]);
});
dynarray_free_data(&stageinfo.stages);
mem_free(stageinfo.stages_progress);
dynstage_shutdown();
}
size_t stageinfo_get_num_stages(void) {
return stageinfo.stages.num_elements;
}
StageInfo *stageinfo_get_by_index(size_t index) {
return dynarray_get_ptr(&stageinfo.stages, index);
}
StageInfo *stageinfo_get_by_id(uint16_t n) {
// TODO speed this up (use a hashtable maybe)
dynarray_foreach_elem(&stageinfo.stages, StageInfo *stg, {
if(stg->id == n) {
return stg;
}
});
return NULL;
}
StageInfo *stageinfo_get_by_spellcard(AttackInfo *spell, Difficulty diff) {
if(!spell) {
return NULL;
}
dynarray_foreach_elem(&stageinfo.stages, StageInfo *stg, {
if(stg->spell == spell && stg->difficulty == diff) {
return stg;
}
});
return NULL;
}
StageProgress *stageinfo_get_progress(StageInfo *stage, Difficulty diff, bool allocate) {
// D_Any stages will have a separate StageProgress for every selectable difficulty.
// Stages with a fixed difficulty setting (spellpractice, extra stage...) obviously get just one and the diff parameter is ignored.
// This stuff must stay around until progress_save(), which happens on shutdown.
// So do NOT try to free any pointers this function returns, that will fuck everything up.
uint idx = dynarray_indexof(&stageinfo.stages, stage);
StageProgress **prog = stageinfo.stages_progress + idx;
bool fixed_diff = (stage->difficulty != D_Any);
if(!fixed_diff && (diff < D_Easy || diff > D_Lunatic)) {
return NULL;
}
if(!*prog) {
if(!allocate) {
return NULL;
}
*prog = ALLOC_ARRAY(fixed_diff ? 1 : NUM_SELECTABLE_DIFFICULTIES, typeof(**prog));
}
return *prog + (fixed_diff ? 0 : diff - D_Easy);
}
StageProgress *stageinfo_get_progress_by_id(uint16_t id, Difficulty diff, bool allocate) {
StageInfo *info = stageinfo_get_by_id(id);
if(info != NULL) {
return stageinfo_get_progress(info, diff, allocate);
}
return NULL;
}