forked from Ports/perfect_dark
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:
1 parent
9b17aaf258
commit
d98575d77b
10 files changed
+597
-82
No files matched your search
@@ -20,4 +20,6 @@ FILE *fsFileOpenWrite(const char *name);
|
||||
FILE *fsFileOpenRead(const char *name);
|
||||
void fsFileClose(FILE *f);
|
||||
|
||||
const char *fsGetModDir(void);
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user