Files
perfect_dark/port/src/mod.c
T

534 lines
14 KiB
C

#include <stdlib.h>
#include <string.h>
#include <PR/ultratypes.h>
#include "platform.h"
#include "system.h"
#include "fs.h"
#include "utils.h"
#include "romdata.h"
#include "mod.h"
#include "data.h"
#include "game/stagetable.h"
#define MOD_TEXTURES_DIR "textures"
#define MOD_ANIMATIONS_DIR "animations"
#define MOD_SEQUENCES_DIR "sequences"
extern struct stagemusic g_StageTracks[];
extern struct stageallocation g_StageAllocations8Mb[];
#define PARSE_STAGE_FLOAT(sec, name, v, min, max) \
p = modConfigParseFloatValue(p, token, &v); \
if (!p || v < (min) || v > (max)) { \
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: " sec " invalid " name " value: %s", stagenum, token); \
return NULL; \
}
#define PARSE_STAGE_INT(sec, name, v, min, max) \
p = modConfigParseIntValue(p, token, &v); \
if (!p || v < (min) || v > (max)) { \
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: " sec " invalid " name " value: %s", stagenum, token); \
return NULL; \
}
#define PARSE_STAGE_FILENAME(sec, name, v) \
p = modConfigParseFileValue(p, token, &v); \
if (!p) { \
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: " sec " invalid " name " value: %s", stagenum, token); \
return NULL; \
}
#define PARSE_STAGE_STRING(sec, name, v) \
p = strParseToken(p, token, NULL); \
if (!p) { \
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: " sec " invalid " name " value: %s", stagenum, token); \
return NULL; \
} \
v = strUnquote(token);
#define PARSE_INT(sec, name, v, min, max, ret) \
p = modConfigParseIntValue(p, token, &v); \
if (!p || v < (min) || v > (max)) { \
sysLogPrintf(LOG_ERROR, "mod: %s: invalid " name " value: %s", sec, token); \
return ret; \
}
static inline char *modConfigParseFileValue(char *p, char *token, s32 *filenum)
{
p = strParseToken(p, token, NULL);
if (!token[0]) {
return NULL; // empty
}
// check if it is a number already
s32 num = strtol(token, NULL, 0);
if (num > 0 && romdataFileGetName(num)) {
*filenum = num;
return p;
}
// it's a filename
num = romdataFileGetNumForName(strUnquote(token));
if (num >= 0) {
*filenum = num;
return p;
}
// the filename was invalid
return NULL;
}
static inline char *modConfigParseIntValue(char *p, char *token, s32 *out)
{
p = strParseToken(p, token, NULL);
if (!token[0]) {
return NULL; // empty
}
char *endp = token;
const s32 num = strtol(token, &endp, 0);
if (num == 0 && (endp == token || *endp != '\0')) {
return NULL;
}
*out = num;
return p;
}
static inline char *modConfigParseFloatValue(char *p, char *token, f32 *out)
{
p = strParseToken(p, token, NULL);
if (!token[0]) {
return NULL; // empty
}
char *endp = token;
const f32 num = strtof(token, &endp);
if (num == 0.f && (endp == token || *endp != '\0')) {
return NULL;
}
*out = num;
return p;
}
static char *modConfigParseStageMusic(char *p, char *token, s32 stagenum)
{
struct stagemusic *smus = NULL;
for (struct stagemusic *p = g_StageTracks; p->stagenum; ++p) {
if (p->stagenum == stagenum) {
smus = p;
break;
}
}
if (!smus) {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: music can't be changed for this stage", stagenum);
return NULL;
}
// eat opening bracket
p = strParseToken(p, token, NULL);
if (token[0] != '{' || token[1] != '\0') {
return NULL;
}
// parse keyvalues until } is reached
s32 tmp = 0;
p = strParseToken(p, token, NULL);
while (p && token[0] && strcmp(token, "}") != 0) {
if (!strcmp(token, "primarytrack")) {
PARSE_STAGE_INT("music:", "primarytrack", tmp, 0, 128);
smus->primarytrack = tmp;
} else if (!strcmp(token, "ambienttrack")) {
PARSE_STAGE_INT("music:", "ambienttrack", tmp, 0, 128);
smus->ambienttrack = tmp;
} else if (!strcmp(token, "xtrack")) {
PARSE_STAGE_INT("music:", "xtrack", tmp, 0, 128);
smus->xtrack = tmp;
} else {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: music: invalid key: %s", stagenum, token);
return NULL;
}
p = strParseToken(p, token, NULL);
}
if (token[0] != '}') {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: unterminated music block", stagenum);
return NULL;
}
return p;
}
static char *modConfigParseStageWeatherRooms(char *p, char *token, s32 stagenum, struct weathercfg *wcfg)
{
// determine where we can start adding rooms
s32 idx;
for (idx = 0; idx < WEATHERCFG_MAX_SKIPROOMS && wcfg->skiprooms[idx]; ++idx);
// eat opening bracket
p = strParseToken(p, token, NULL);
if (token[0] != '{' || token[1] != '\0') {
return NULL;
}
// check if user wants to clear the whole list
p = strParseToken(p, token, NULL);
if (!strcmp(token, "clear")) {
memset(wcfg->skiprooms, 0, sizeof(wcfg->skiprooms));
idx = 0;
p = strParseToken(p, token, NULL);
}
s32 tmp = 0;
while (p && token[0] && strcmp(token, "}") != 0) {
if (token[0] == ',' && !token[1]) {
p = strParseToken(p, token, NULL);
continue;
}
tmp = strtol(token, NULL, 0);
if (tmp <= 0 || tmp > 32767) {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: weather: rooms: invalid room %s", stagenum, token);
return NULL;
}
if (idx < WEATHERCFG_MAX_SKIPROOMS) {
wcfg->skiprooms[idx++] = tmp;
}
p = strParseToken(p, token, NULL);
}
if (token[0] != '}') {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: weather: unterminated rooms block", stagenum);
return NULL;
}
return p;
}
static char *modConfigParseStageWeather(char *p, char *token, s32 stagenum)
{
s32 wi;
struct weathercfg *wcfg = NULL;
for (wi = 0; wi < ARRAYCOUNT(g_WeatherConfig) && g_WeatherConfig[wi].stagenum; ++wi) {
if (g_WeatherConfig[wi].stagenum == stagenum) {
break;
}
}
if (wi >= WEATHERCFG_MAX_STAGES) {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: no more space for weather config", stagenum);
return NULL;
}
wcfg = &g_WeatherConfig[wi];
if (!wcfg->stagenum) {
// new weather config; initialize with defaults
*wcfg = g_DefaultWeatherConfig;
wcfg->stagenum = stagenum;
} else {
// flags have to be re-specified
wcfg->flags = 0;
}
// eat opening bracket
p = strParseToken(p, token, NULL);
if (token[0] != '{' || token[1] != '\0') {
return NULL;
}
// parse keyvalues until } is reached
s32 tmpi = 0;
f32 tmpf = 0.f;
p = strParseToken(p, token, NULL);
while (p && token[0] && strcmp(token, "}") != 0) {
if (!strcmp(token, "include_rooms") || !strcmp(token, "exclude_rooms")) {
// include_rooms | exclude_rooms { ROOM_NUMBERS... }
const s32 include = (token[0] == 'i');
p = modConfigParseStageWeatherRooms(p, token, stagenum, wcfg);
if (!p) {
return NULL;
}
if (wcfg->skiprooms[0] && include) {
wcfg->flags |= WEATHERFLAG_INCLUDE;
}
} else if (!strcmp(token, "cutscene_only")) {
wcfg->flags |= WEATHERFLAG_CUTSCENE_ONLY;
} else if (!strcmp(token, "constant_wind")) {
PARSE_STAGE_FLOAT("weather:", "constant_wind (0)", wcfg->windanglerad, -M_TAU, M_TAU);
PARSE_STAGE_FLOAT("weather:", "constant_wind (1)", wcfg->windspeedx, -1024.f, 1024.f);
PARSE_STAGE_FLOAT("weather:", "constant_wind (2)", wcfg->windspeedz, -1024.f, 1024.f);
wcfg->flags |= WEATHERFLAG_FORCE_WINDDIR;
} else if (!strcmp(token, "windspeed")) {
PARSE_STAGE_FLOAT("weather:", "windspeed", tmpf, -1024.f, 1024.f);
wcfg->windspeed = tmpf;
} else if (!strcmp(token, "ymin")) {
PARSE_STAGE_FLOAT("weather:", "ymin", tmpf, -65536.f, 65536.f);
wcfg->ymin = tmpf;
} else if (!strcmp(token, "ymax")) {
PARSE_STAGE_FLOAT("weather:", "ymax", tmpf, -65536.f, 65536.f);
wcfg->ymax = tmpf;
} else if (!strcmp(token, "zmax")) {
PARSE_STAGE_FLOAT("weather:", "zmax", tmpf, -65536.f, 65536.f);
wcfg->zmax = tmpf;
} else {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: weather: invalid key: %s", stagenum, token);
return NULL;
}
p = strParseToken(p, token, NULL);
}
if (token[0] != '}') {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: unterminated weather block", stagenum);
return NULL;
}
return p;
}
static char *modConfigParseStage(char *p, char *token)
{
// stage number
p = strParseToken(p, token, NULL);
const s32 stagenum = strtol(token, NULL, 0);
if (stagenum <= 0x01 || stagenum > 0x50) {
return NULL;
}
// eat opening bracket
p = strParseToken(p, token, NULL);
if (token[0] != '{' || token[1] != '\0') {
return NULL;
}
// find the stage table pointers this corresponds to
struct stagetableentry *stab = NULL;
struct stageallocation *salloc = NULL;
const s32 sidx = stageGetIndex(stagenum);
if (sidx >= 0) {
stab = &g_Stages[sidx];
} else {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: unknown stage number", stagenum);
return NULL;
}
for (struct stageallocation *p = g_StageAllocations8Mb; p->stagenum; ++p) {
if (p->stagenum == stagenum) {
salloc = p;
break;
}
}
// parse keyvalues until } is reached
s32 tmp = 0;
char *tmps = NULL;
p = strParseToken(p, token, NULL);
while (p && token[0] && strcmp(token, "}") != 0) {
if (!strcmp(token, "bgfile")) {
// bg FILE_NAME_OR_NUM
PARSE_STAGE_FILENAME("", "bgfile", tmp);
stab->bgfileid = tmp;
} else if (!strcmp(token, "tilesfile")) {
// tilesfile FILE_NAME_OR_NUM
PARSE_STAGE_FILENAME("", "tilesfile", tmp);
stab->tilefileid = tmp;
} else if (!strcmp(token, "padsfile")) {
// padsfile FILE_NAME_OR_NUM
PARSE_STAGE_FILENAME("", "padsfile", tmp);
stab->padsfileid = tmp;
} else if (!strcmp(token, "setupfile")) {
// setupfile FILE_NAME_OR_NUM
PARSE_STAGE_FILENAME("", "setupfile", tmp);
stab->setupfileid = tmp;
} else if (!strcmp(token, "mpsetupfile")) {
// mpsetupfile FILE_NAME_OR_NUM
PARSE_STAGE_FILENAME("", "mpsetupfile", tmp);
stab->mpsetupfileid = tmp;
} else if (!strcmp(token, "alarm")) {
PARSE_STAGE_INT("", "alarm", tmp, 1, 0xFFFF);
stab->alarm = tmp;
} else if (!strcmp(token, "extragunmem")) {
PARSE_STAGE_INT("", "extragunmem", tmp, 0, 0xFFFF);
stab->extragunmem = tmp;
} else if (!strcmp(token, "allocation")) {
// allocation "ALLOCSTRING"
PARSE_STAGE_STRING("", "allocation", tmps);
// FIXME: this leaks
tmps = strDuplicate(tmps);
if (tmps) {
salloc->string = tmps;
}
} else if (!strcmp(token, "music")) {
// music { KEYVALUES... }
p = modConfigParseStageMusic(p, token, stagenum);
if (!p) {
return NULL;
}
} else if (!strcmp(token, "weather")) {
// weather { KEYVALUES... }
p = modConfigParseStageWeather(p, token, stagenum);
if (!p) {
return NULL;
}
} else {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: invalid key: %s", stagenum, token);
return NULL;
}
p = strParseToken(p, token, NULL);
}
if (token[0] != '}') {
sysLogPrintf(LOG_ERROR, "modconfig: unterminated stage 0x%02x block", stagenum);
return NULL;
}
return p;
}
s32 modConfigLoad(const char *fname)
{
u32 dataLen = 0;
char *data = fsFileLoad(fname, &dataLen);
if (!data) {
return false;
}
s32 success = true;
char token[UTIL_MAX_TOKEN + 1] = { 0 };
char *end = data + dataLen;
char *p = strParseToken(data, token, NULL);
while (p && token[0]) {
if (!strcmp(token, "stage")) {
// stage NUMBER { KEYVALUES... }
char *prev = p;
p = modConfigParseStage(p, token);
if (!p) {
sysLogPrintf(LOG_ERROR, "modconfig: malformed stage block at offset %d", prev - data);
success = false;
break;
}
} else {
// garbage
sysLogPrintf(LOG_ERROR, "modconfig: unexpected %s at offset %d", token[0] ? token : "end of file", p - data);
success = false;
break;
}
p = strParseToken(p, token, NULL);
}
sysMemFree(data);
return success;
}
s32 modTextureLoad(u16 num, void *dst, u32 dstSize)
{
static s32 dirExists = -1;
if (dirExists < 0) {
dirExists = (fsFileSize(MOD_TEXTURES_DIR "/") >= 0);
}
if (!dirExists) {
return -1;
}
char path[FS_MAXPATH + 1];
snprintf(path, sizeof(path), MOD_TEXTURES_DIR "/%04x.bin", num);
const s32 ret = fsFileLoadTo(path, dst, dstSize);
if (ret > 0) {
sysLogPrintf(LOG_NOTE, "mod: loaded external texture %04x", num);
}
return ret;
}
void *modSequenceLoad(u16 num, u32 *outSize)
{
static s32 dirExists = -1;
if (dirExists < 0) {
dirExists = (fsFileSize(MOD_SEQUENCES_DIR "/") >= 0);
}
if (!dirExists) {
return NULL;
}
char path[FS_MAXPATH + 1];
snprintf(path, sizeof(path), MOD_SEQUENCES_DIR "/%04x.bin", num);
if (fsFileSize(path) > 0) {
void *ret = fsFileLoad(path, outSize);
if (ret) {
sysLogPrintf(LOG_NOTE, "mod: loaded external sequence %04x", num);
return ret;
}
}
return NULL;
}
void *modAnimationLoadData(u16 num)
{
char path[FS_MAXPATH + 1];
// load the animation data
snprintf(path, sizeof(path), MOD_ANIMATIONS_DIR "/%04x.bin", num);
void *data = fsFileLoad(path, NULL);
if (!data) {
sysFatalError("External animation %04x has no data file.\nEnsure that it is placed at %s or delete the descriptor.", num, path);
}
return data;
}
s32 modAnimationLoadDescriptor(u16 num, struct animtableentry *anim)
{
static s32 dirExists = -1;
if (dirExists < 0) {
dirExists = (fsFileSize(MOD_ANIMATIONS_DIR "/") >= 0);
}
if (!dirExists) {
return false;
}
char path[FS_MAXPATH + 1];
// load the descriptor, if any
snprintf(path, sizeof(path), MOD_ANIMATIONS_DIR "/%04x.txt", num);
if (fsFileSize(path) <= 0) {
return false;
}
char *desc = fsFileLoad(path, NULL);
if (!desc) {
return false;
}
// parse the descriptor
char token[UTIL_MAX_TOKEN + 1] = { 0 };
char *p = strParseToken(desc, token, NULL);
s32 tmp = 0;
while (p && token[0]) {
if (!strcmp(token, "numframes")) {
PARSE_INT(path, "numframes", tmp, 0, 0xFFFF, false);
anim->numframes = tmp;
} else if (!strcmp(token, "bytesperframe")) {
PARSE_INT(path, "bytesperframe", tmp, 0, 0xFFFF, false);
anim->bytesperframe = tmp;
} else if (!strcmp(token, "headerlen")) {
PARSE_INT(path, "headerlen", tmp, 0, 0xFFFF, false);
anim->headerlen = tmp;
} else if (!strcmp(token, "framelen")) {
PARSE_INT(path, "framelen", tmp, 0, 0xFF, false);
anim->framelen = tmp;
} else if (!strcmp(token, "flags")) {
PARSE_INT(path, "flags", tmp, 0, 0xFF, false);
anim->flags = tmp;
} else {
sysLogPrintf(LOG_ERROR, "mod: %s: invalid key: %s", path, token);
return false;
}
p = strParseToken(p, token, NULL);
}
sysMemFree(desc);
sysLogPrintf(LOG_NOTE, "mod: loaded external animation %04x", num);
return true;
}