port: add basic mod directory support with config files for custom stages

select mod directory with --moddir whatever

these should contain a modconfig.txt that can specify replacements for some stage table fields, etc
This commit is contained in:
fgsfds committed 2023-09-26 23:05:07 +02:00
1 parent 9b17aaf258
commit d98575d77b
10 files changed
+597 -82

No files matched your search

+2
View File
@@ -20,4 +20,6 @@ FILE *fsFileOpenWrite(const char *name);
FILE *fsFileOpenRead(const char *name);
void fsFileClose(FILE *f);
const char *fsGetModDir(void);
#endif
+10
View File
@@ -0,0 +1,10 @@
#ifndef _IN_MOD_H
#define _IN_MOD_H
#include <PR/ultratypes.h>
#define MOD_CONFIG_FNAME "modconfig.txt"
s32 modConfigLoad(const char *path);
#endif
+3
View File
@@ -11,10 +11,13 @@ s32 romdataInit(void);
u8 *romdataFileLoad(s32 fileNum, u32 *outSize);
void romdataFilePreprocess(s32 fileNum, s32 loadType, u8 *data, u32 size);
void romdataFileFree(s32 fileNum);
const char *romdataFileGetName(s32 fileNum);
u8 *romdataFileGetData(s32 fileNum);
s32 romdataFileGetSize(s32 fileNum);
s32 romdataFileGetNumForName(const char *name);
u8 *romdataSegGetData(const char *segName);
u8 *romdataSegGetDataEnd(const char *segName);
u32 romdataSegGetSize(const char *segName);
+15
View File
@@ -0,0 +1,15 @@
#ifndef _IN_UTILS_H
#define _IN_UTILS_H
#define UTIL_MAX_TOKEN 1024
#include <PR/ultratypes.h>
char *strRightTrim(char *str);
char *strTrim(char *str);
char *strUnquote(char *str);
char *strParseToken(char *str, char *out, s32 *outCount);
char *strFmt(const char *fmt, ...);
char *strDuplicate(const char *str);
#endif
+25 -64
View File
@@ -6,6 +6,7 @@
#include "fs.h"
#include "config.h"
#include "system.h"
#include "utils.h"
#define CONFIG_MAX_STR 512
#define CONFIG_MAX_SECNAME 128
@@ -216,6 +217,7 @@ s32 configLoad(const char *fname)
char curSec[CONFIG_MAX_SECNAME + 1] = { 0 };
char keyBuf[CONFIG_MAX_SECNAME * 2 + 2] = { 0 }; // SECTION + . + KEY + \0
char token[UTIL_MAX_TOKEN + 1] = { 0 };
char lineBuf[2048] = { 0 };
char *line = lineBuf;
s32 lineLen = 0;
@@ -223,77 +225,36 @@ s32 configLoad(const char *fname)
while (fgets(lineBuf, sizeof(lineBuf), f)) {
line = lineBuf;
// left-trim whitespace
while (*line && isspace(*line)) {
++line;
}
line = strParseToken(line, token, NULL);
lineLen = strlen(line);
if (!lineLen) {
continue;
}
// right-trim whitespace and \n
for (s32 i = lineLen - 1; i >= 0 && isspace(line[i]); --i, --lineLen);
line[lineLen] = '\0';
if (line[0] == ';' || line[0] == '#') {
// comment; skip the rest of the line
continue;
} else if (line[0] == '[') {
// section; skip [ and find matching ]
++line;
char *end = line;
while (*end && *end != ']') {
++end;
}
// found anything?
if (*end != ']') {
// didn't find shit
if (token[0] == '[' && token[1] == '\0') {
// section; get name
line = strParseToken(line, token, NULL);
if (!token[0]) {
sysLogPrintf(LOG_ERROR, "configLoad: malformed section line: %s", lineBuf);
continue;
}
// eat ] and whitespace on the right
while (end > line && isspace(end[-1])) {
--end;
strncpy(curSec, token, CONFIG_MAX_SECNAME);
// eat ]
line = strParseToken(line, token, NULL);
if (token[0] != ']' || token[1] != '\0') {
sysLogPrintf(LOG_ERROR, "configLoad: malformed section line: %s", lineBuf);
}
*end = '\0';
// empty []?
if (end == line) {
} else if (token[0]) {
// probably a key=value pair; append key name to section name
snprintf(keyBuf, sizeof(keyBuf) - 1, "%s.%s", curSec, token);
// eat =
line = strParseToken(line, token, NULL);
if (token[0] != '=' || token[1] != '\0') {
sysLogPrintf(LOG_ERROR, "configLoad: malformed keyvalue line: %s", lineBuf);
continue;
}
// eat whitespace on the left
while (line < end && isspace(*line)) {
++line;
// the rest of the line is the value
line = strTrim(line);
if (line[0] == '"') {
line = strUnquote(line);
}
// copy out the section name
const s32 len = end - line;
if (len > CONFIG_MAX_SECNAME) {
continue; // too long
}
memcpy(curSec, line, len);
curSec[len] = '\0';
} else {
// probably a key=value pair
char *eq = strchr(line, '=');
if (!eq) {
continue; // garbage
}
char *value = eq + 1;
if (!*value) {
continue; // just a =
}
// trim right whitespace on the key
--eq;
while (eq > line && isspace(*eq)) {
--eq;
}
if (eq == line) {
continue; // = with nothing on the left
}
eq[1] = '\0';
// assemble full key name and read the value
snprintf(keyBuf, sizeof(keyBuf) - 1, "%s.%s", curSec, line);
configSetFromString(keyBuf, value);
configSetFromString(keyBuf, line);
}
}
+59 -17
View File
@@ -9,11 +9,13 @@
#include "config.h"
#include "system.h"
#include "platform.h"
#include "utils.h"
#include "fs.h"
#define DEFAULT_DATADIR_NAME "data"
#define DEFAULT_BASEDIR_NAME "data"
static char dataDir[FS_MAXPATH + 1]; // replaces $D
static char baseDir[FS_MAXPATH + 1]; // replaces $B
static char modDir[FS_MAXPATH + 1]; // replaces $M
static char saveDir[FS_MAXPATH + 1]; // replaces $S
static char homeDir[FS_MAXPATH + 1]; // replaces $H
static char exeDir[FS_MAXPATH + 1]; // replaces $E
@@ -57,7 +59,8 @@ const char *fsFullPath(const char *relPath)
switch (relPath[1]) {
case 'E': expStr = exeDir; break;
case 'H': expStr = homeDir; break;
case 'D': expStr = dataDir; break;
case 'M': expStr = modDir; break;
case 'B': expStr = baseDir; break;
case 'S': expStr = saveDir; break;
default: break;
}
@@ -71,13 +74,20 @@ const char *fsFullPath(const char *relPath)
}
// couldn't expand anything, return as is
return relPath;
} else if (!dataDir[0] || fsPathIsAbsolute(relPath) || fsPathIsCwdRelative(relPath)) {
// user explicitly wants working directory or this is an absolute path or we have no dataDir set up yet
} else if (!baseDir[0] || fsPathIsAbsolute(relPath) || fsPathIsCwdRelative(relPath)) {
// user explicitly wants working directory or this is an absolute path or we have no baseDir set up yet
return relPath;
}
// path relative to data dir
snprintf(pathBuf, FS_MAXPATH, "%s/%s", dataDir, relPath);
// path relative to mod or base dir; this will be a read request, so check where the file actually is
if (modDir[0]) {
snprintf(pathBuf, FS_MAXPATH, "%s/%s", modDir, relPath);
if (fsFileSize(pathBuf) >= 0) {
return pathBuf;
}
}
// fall back to basedir
snprintf(pathBuf, FS_MAXPATH, "%s/%s", baseDir, relPath);
return pathBuf;
}
@@ -93,21 +103,45 @@ s32 fsInit(void)
sysGetHomePath(homeDir, FS_MAXPATH);
}
// get path to data dir and expand it if needed
const char *path = sysArgGetString("--datadir");
// get path to base dir and expand it if needed
const char *path = sysArgGetString("--basedir");
if (!path) {
// check if there's a data directory in working directory or homeDir, otherwise default to exe directory
path = "$E/" DEFAULT_DATADIR_NAME;
// check if there's a `data` directory in working directory or homeDir, otherwise default to exe directory
path = "$E/" DEFAULT_BASEDIR_NAME;
if (!portable) {
if (fsFileSize("./" DEFAULT_DATADIR_NAME) >= 0) {
path = "./" DEFAULT_DATADIR_NAME;
} else if (fsFileSize("$H/" DEFAULT_DATADIR_NAME)) {
path = "$H/" DEFAULT_DATADIR_NAME;
if (fsFileSize("./" DEFAULT_BASEDIR_NAME) >= 0) {
path = "./" DEFAULT_BASEDIR_NAME;
} else if (fsFileSize("$H/" DEFAULT_BASEDIR_NAME)) {
path = "$H/" DEFAULT_BASEDIR_NAME;
}
}
}
strncpy(baseDir, fsFullPath(path), FS_MAXPATH);
strncpy(dataDir, fsFullPath(path), FS_MAXPATH);
// get path to mod dir and expand it if needed
// mod directory is overlaid on top of base directory
path = sysArgGetString("--moddir");
if (path) {
if (fsPathIsAbsolute(path) || fsPathIsCwdRelative(path) || path[0] == '$') {
// path is explicit; check as-is
if (fsFileSize(path) >= 0) {
strncpy(modDir, fsFullPath(path), FS_MAXPATH);
}
} else {
// path is relative to workdir; try to find it
const char *priority[] = { ".", "$E", "$H" };
for (s32 i = 0; i < 2 + (portable != 0); ++i) {
char *tmp = strFmt("%s/%s", priority[i], path);
if (fsFileSize(tmp) >= 0) {
strncpy(modDir, fsFullPath(tmp), FS_MAXPATH);
break;
}
}
}
if (!modDir[0]) {
sysLogPrintf(LOG_WARNING, "could not find specified moddir `%s`", path);
}
}
// get path to save dir and expand it if needed
path = sysArgGetString("--savedir");
@@ -136,12 +170,20 @@ s32 fsInit(void)
strncpy(saveDir, fsFullPath(path), FS_MAXPATH);
sysLogPrintf(LOG_NOTE, "data dir: %s", dataDir);
if (modDir[0]) {
sysLogPrintf(LOG_NOTE, " mod dir: %s", modDir);
}
sysLogPrintf(LOG_NOTE, "base dir: %s", baseDir);
sysLogPrintf(LOG_NOTE, "save dir: %s", saveDir);
return 0;
}
const char *fsGetModDir(void)
{
return modDir[0] ? modDir : NULL;
}
void *fsFileLoad(const char *name, u32 *outSize)
{
const char *fullName = fsFullPath(name);
+5
View File
@@ -14,6 +14,7 @@
#include "fs.h"
#include "romdata.h"
#include "config.h"
#include "mod.h"
#include "system.h"
u32 g_OsMemSize = 0;
@@ -95,6 +96,10 @@ int main(int argc, const char **argv)
gameLoadConfig();
if (fsGetModDir()) {
modConfigLoad(MOD_CONFIG_FNAME);
}
atexit(cleanup);
bootCreateSched();
+256
View File
@@ -0,0 +1,256 @@
#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"
extern struct stagemusic g_StageTracks[];
extern struct stageallocation g_StageAllocations8Mb[];
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 *modConfigParseNumericValue(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 *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")) {
p = modConfigParseNumericValue(p, token, &tmp);
if (!p || tmp < 0 || tmp > 128) {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: music: invalid primarytrack value: %s", stagenum, token);
return NULL;
}
smus->primarytrack = tmp;
} else if (!strcmp(token, "ambienttrack")) {
p = modConfigParseNumericValue(p, token, &tmp);
if (!p || tmp < 0 || tmp > 128) {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: music: invalid ambienttrack value: %s", stagenum, token);
return NULL;
}
smus->ambienttrack = tmp;
} else if (!strcmp(token, "xtrack")) {
p = modConfigParseNumericValue(p, token, &tmp);
if (!p || tmp < 0 || tmp > 128) {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: music: invalid xtrack value: %s", stagenum, token);
return NULL;
}
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 inline 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];
}
for (struct stageallocation *p = g_StageAllocations8Mb; p->stagenum; ++p) {
if (p->stagenum == stagenum) {
salloc = p;
break;
}
}
// parse keyvalues until } is reached
s32 tmp = 0;
p = strParseToken(p, token, NULL);
while (p && token[0] && strcmp(token, "}") != 0) {
if (!strcmp(token, "bgfile")) {
// bg FILE_NAME_OR_NUM
p = modConfigParseFileValue(p, token, &tmp);
if (p && stab) {
stab->bgfileid = tmp;
} else {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: invalid bgfile value: %s", stagenum, token);
return NULL;
}
} else if (!strcmp(token, "tilesfile")) {
// tiles FILE_NAME_OR_NUM
p = modConfigParseFileValue(p, token, &tmp);
if (p && stab) {
stab->tilefileid = tmp;
} else {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: invalid tilesfile value: %s", stagenum, token);
return NULL;
}
} else if (!strcmp(token, "padsfile")) {
// tiles FILE_NAME_OR_NUM
p = modConfigParseFileValue(p, token, &tmp);
if (p && stab) {
stab->padsfileid = tmp;
} else {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: invalid padsfile value: %s", stagenum, token);
return NULL;
}
} else if (!strcmp(token, "setupfile")) {
// setup FILE_NAME_OR_NUM
p = modConfigParseFileValue(p, token, &tmp);
if (p && stab) {
stab->setupfileid = tmp;
} else {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: invalid setupfile value: %s", stagenum, token);
return NULL;
}
} else if (!strcmp(token, "mpsetupfile")) {
// mpsetup FILE_NAME_OR_NUM
p = modConfigParseFileValue(p, token, &tmp);
if (p && stab) {
stab->mpsetupfileid = tmp;
} else {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: invalid mpsetupfile value: %s", stagenum, token);
return NULL;
}
} else if (!strcmp(token, "allocation")) {
// allocation "ALLOCSTRING"
p = strParseToken(p, token, NULL);
char *str = strUnquote(token);
if (!p || !str[0] || !salloc) {
sysLogPrintf(LOG_ERROR, "modconfig: stage 0x%02x: invalid allocation value: %s", stagenum, token);
return NULL;
}
// FIXME: this leaks
str = strDuplicate(str);
if (str) {
salloc->string = str;
}
} else if (!strcmp(token, "music")) {
// music { KEYVALUES... }
p = modConfigParseStageMusic(p, token, stagenum);
if (!p) {
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;
}
+24 -1
View File
@@ -431,13 +431,36 @@ void romdataFileFree(s32 fileNum)
}
if (fileSlots[fileNum].source == SRC_EXTERNAL) {
free(fileSlots[fileNum].data);
sysMemFree(fileSlots[fileNum].data);
fileSlots[fileNum].data = NULL;
}
fileSlots[fileNum].source = SRC_UNLOADED;
}
const char *romdataFileGetName(s32 fileNum)
{
if (fileNum < 1 || fileNum >= ROMDATA_MAX_FILES) {
return NULL;
}
return fileSlots[fileNum].name;
}
s32 romdataFileGetNumForName(const char *name)
{
if (!name || !name[0]) {
return -1;
}
for (s32 i = 0; i < ROMDATA_MAX_FILES; ++i) {
if (fileSlots[i].name && !strcmp(fileSlots[i].name, name)) {
return i;
}
}
return -1;
}
u8 *romdataSegGetData(const char *segName)
{
return romdataGetSeg(segName)->data;
+198
View File
@@ -0,0 +1,198 @@
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <PR/ultratypes.h>
#include "platform.h"
#include "system.h"
#include "utils.h"
static inline bool isSingleCharToken(const s32 ch)
{
switch (ch) {
case '{':
case '}':
case '[':
case ']':
case '(':
case ')':
case ',':
case '\'':
case '=':
return true;
default:
return false;
}
}
char *strFmt(const char *fmt, ...)
{
static char buf[4096];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
return buf;
}
char *strRightTrim(char *str)
{
if (!str) {
return NULL;
}
const s32 len = strlen(str);
for (s32 i = len - 1; i >= 0 && isspace(str[i]); --i) {
str[i] = '\0';
}
return str;
}
char *strTrim(char *str)
{
if (!str) {
return NULL;
}
// left trim
while (*str && (u8)*str < ' ') {
++str;
}
// right trim
const s32 len = strlen(str);
for (s32 i = len - 1; i > 0 && isspace(str[i]); --i) {
str[i] = '\0';
}
return str;
}
char *strUnquote(char *str)
{
if (!str) {
return NULL;
}
if (*str == '"') {
++str;
}
char *end = strrchr(str, '"');
if (end && end[1] == '\0') {
*end = '\0';
}
return str;
}
char *strParseToken(char *str, char *out, s32 *outCount)
{
if (outCount) {
*outCount = 0;
}
if (!out) {
return NULL;
}
out[0] = '\0';
if (!str) {
return NULL;
}
s32 cnt = 0;
while (*str) {
// skip whitespace and other garbage
while (*str && (u8)*str <= ' ') {
++str;
}
if (!*str) {
// just whitespace
return NULL;
}
// skip #, ; and // comments
if (*str == ';' || *str == '#' || (str[0] == '/' && str[1] == '/')) {
while (*str && *str != '\n') {
++str;
}
continue;
}
if (*str == '"') {
// quoted string; treat it as an entire token, including the quotes
out[cnt++] = *str++;
while (true) {
if (*str == '\0') {
// unterminated quoted string
break;
}
if (str[0] == '\\' && str[1] == '"') {
// escaped quote; add to token
if (cnt + 1 < UTIL_MAX_TOKEN) {
out[cnt++] = str[1];
}
str += 2;
continue;
}
// add char to token
const s32 ch = (u8)*str++;
if (cnt + 1 < UTIL_MAX_TOKEN) {
out[cnt++] = ch;
}
if (ch == '"') {
// ch was the closing quote
break;
}
}
break;
}
if (isSingleCharToken(*str)) {
// single char token
out[cnt++] = *str++;
break;
}
// regular single word
do {
if (cnt + 1 < UTIL_MAX_TOKEN) {
out[cnt++] = *str++;
}
if (isSingleCharToken(*str)) {
break;
}
} while ((u8)*str > ' ');
break;
}
out[cnt] = '\0';
if (outCount) {
*outCount = cnt;
}
return str;
}
char *strDuplicate(const char *str)
{
if (!str) {
return NULL;
}
const u32 len = strlen(str);
char *out = sysMemAlloc(len + 1);
if (out) {
memcpy(out, str, len + 1);
}
return out;
}