mkxp-z/src/config.cpp
2022-01-23 08:41:32 -05:00

354 lines
10 KiB
C++

//
// config.cpp
// Player
//
// Created by ゾロアーク on 11/21/20.
//
#include "config.h"
#include <SDL_filesystem.h>
#include <assert.h>
#include <stdint.h>
#include <vector>
#include "filesystem/filesystem.h"
#include "util/exception.h"
#include "util/debugwriter.h"
#include "util/sdl-util.h"
#include "util/util.h"
#include "util/json5pp.hpp"
#include "util/iniconfig.h"
#include "util/encoding.h"
#include "system/system.h"
namespace json = json5pp;
std::string prefPath(const char *org, const char *app) {
char *path = SDL_GetPrefPath(org, app);
if (!path)
return std::string("");
std::string ret(path);
SDL_free(path);
return ret;
}
void fillStringVec(json::value &item, std::vector<std::string> &vector) {
if (!item.is_array()) {
if (item.is_string()) {
vector.push_back(item.as_string());
}
return;
}
auto &array = item.as_array();
for (size_t i = 0; i < array.size(); i++) {
if (!array[i].is_string())
continue;
vector.push_back(array[i].as_string());
}
}
bool copyObject(json::value &dest, json::value &src, const char *objectName = "") {
assert(dest.is_object());
if (src.is_null())
return false;
if (!src.is_object())
return false;
auto &srcVec = src.as_object();
auto &destVec = dest.as_object();
for (auto it : srcVec) {
// Specifically processs this object later.
if (it.second.is_object() && destVec[it.first].is_object())
continue;
if ((it.second.is_array() && destVec[it.first].is_array()) ||
(it.second.is_number() && destVec[it.first].is_number()) ||
(it.second.is_string() && destVec[it.first].is_string()) ||
(it.second.is_boolean() && destVec[it.first].is_boolean()) )
{
destVec[it.first] = it.second;
}
else {
Debug() << "Invalid or unrecognized variable in configuration:" << objectName << it.first;
}
}
return true;
}
#define CONF_FILE "mkxp.json"
Config::Config() {}
void Config::read(int argc, char *argv[]) {
auto optsJ = json::object({
{"rgssVersion", 0},
{"preferMetalRenderer", true},
{"debugMode", false},
{"printFPS", false},
{"winResizable", true},
{"fullscreen", false},
{"fixedAspectRatio", true},
{"smoothScaling", false},
{"vsync", false},
{"defScreenW", 0},
{"defScreenH", 0},
{"windowTitle", ""},
{"fixedFramerate", 0},
{"frameSkip", false},
{"syncToRefreshrate", false},
{"solidFonts", false},
{"subImageFix", false},
{"enableBlitting", true},
{"maxTextureSize", 0},
{"gameFolder", ""},
{"anyAltToggleFS", false},
{"enableReset", true},
{"allowSymlinks", false},
{"dataPathOrg", ""},
{"dataPathApp", ""},
{"iconPath", ""},
{"execName", "Game"},
{"midiSoundFont", ""},
{"midiChorus", false},
{"midiReverb", false},
{"SESourceCount", 6},
{"customScript", ""},
{"pathCache", true},
{"encryptedGraphics", true},
{"useScriptNames", 1},
{"preloadScript", json::array({})},
{"RTP", json::array({})},
{"fontSub", json::array({})},
{"rubyLoadpath", json::array({})},
{"JITEnable", false},
{"JITVerboseLevel", 0},
{"JITMaxCache", 100},
{"JITMinCalls", 10000},
{"bindingNames", json::object({
{"a", "A"},
{"b", "B"},
{"c", "C"},
{"x", "X"},
{"y", "Y"},
{"z", "Z"},
{"l", "L"},
{"r", "R"}
})}
});
auto &opts = optsJ.as_object();
#define GUARD(exp) \
try { exp } catch (...) {}
editor.debug = false;
editor.battleTest = false;
if (argc > 1) {
if (!strcmp(argv[1], "debug") || !strcmp(argv[1], "test"))
editor.debug = true;
else if (!strcmp(argv[1], "btest"))
editor.battleTest = true;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "debug"))
launchArgs.push_back(argv[i]);
}
}
if (mkxp_fs::fileExists(CONF_FILE)) {
json::value confData = json::value(0);
try {
std::string cfg = mkxp_fs::contentsOfFileAsString(CONF_FILE);
confData = json::parse5(Encoding::convertString(cfg));
}
catch (const std::exception &e) {
Debug() << "Failed to parse JSON configuration:" << e.what();
}
catch (const Exception &e) {
Debug() << "Failed to parse JSON configuration: Unknown encoding";
}
if (!confData.is_object())
confData = json::object({});
raw = confData;
copyObject(optsJ, confData);
copyObject(opts["bindingNames"], confData.as_object()["bindingNames"], "bindingNames .");
}
else {
raw = json::object({});
}
#define SET_OPT_CUSTOMKEY(var, key, type) GUARD(var = opts[#key].as_##type();)
#define SET_OPT(var, type) SET_OPT_CUSTOMKEY(var, var, type)
#define SET_STRINGOPT(var, key) GUARD(var = std::string(opts[#key].as_string());)
SET_OPT(rgssVersion, integer);
SET_OPT(preferMetalRenderer, boolean);
SET_OPT(debugMode, boolean);
SET_OPT(printFPS, boolean);
SET_OPT(fullscreen, boolean);
SET_OPT(fixedAspectRatio, boolean);
SET_OPT(smoothScaling, boolean);
SET_OPT(winResizable, boolean);
SET_OPT(vsync, boolean);
SET_OPT(defScreenW, integer);
SET_OPT(defScreenH, integer);
SET_STRINGOPT(windowTitle, windowTitle);
SET_OPT(fixedFramerate, integer);
SET_OPT(frameSkip, boolean);
SET_OPT(syncToRefreshrate, boolean);
SET_OPT(solidFonts, boolean);
SET_OPT(subImageFix, boolean);
SET_OPT(enableBlitting, boolean);
SET_OPT(maxTextureSize, integer);
SET_STRINGOPT(gameFolder, gameFolder);
SET_OPT(anyAltToggleFS, boolean);
SET_OPT(enableReset, boolean);
SET_OPT(allowSymlinks, boolean);
SET_STRINGOPT(dataPathOrg, dataPathOrg);
SET_STRINGOPT(dataPathApp, dataPathApp);
SET_STRINGOPT(iconPath, iconPath);
SET_STRINGOPT(execName, execName);
SET_STRINGOPT(midi.soundFont, midiSoundFont);
SET_OPT_CUSTOMKEY(midi.chorus, midiChorus, boolean);
SET_OPT_CUSTOMKEY(midi.reverb, midiReverb, boolean);
SET_OPT_CUSTOMKEY(SE.sourceCount, SESourceCount, integer);
SET_STRINGOPT(customScript, customScript);
SET_OPT(pathCache, boolean);
SET_OPT(useScriptNames, boolean);
SET_OPT_CUSTOMKEY(jit.enabled, JITEnable, boolean);
SET_OPT_CUSTOMKEY(jit.verboseLevel, JITVerboseLevel, integer);
SET_OPT_CUSTOMKEY(jit.maxCache, JITMaxCache, integer);
SET_OPT_CUSTOMKEY(jit.minCalls, JITMinCalls, integer);
fillStringVec(opts["preloadScript"], preloadScripts);
fillStringVec(opts["RTP"], rtps);
fillStringVec(opts["fontSub"], fontSubs);
fillStringVec(opts["rubyLoadpath"], rubyLoadpaths);
auto &bnames = opts["bindingNames"].as_object();
#define BINDING_NAME(btn) kbActionNames.btn = bnames[#btn].as_string()
BINDING_NAME(a);
BINDING_NAME(b);
BINDING_NAME(c);
BINDING_NAME(x);
BINDING_NAME(y);
BINDING_NAME(z);
BINDING_NAME(l);
BINDING_NAME(r);
rgssVersion = clamp(rgssVersion, 0, 3);
SE.sourceCount = clamp(SE.sourceCount, 1, 64);
// Determine whether to open a console window on... Windows
const char *consoleEnv = SDL_getenv("MKXPZ_WINDOWS_CONSOLE");
winConsole = ((consoleEnv && !strcmp(consoleEnv, "1")) || editor.debug);
#ifdef __APPLE__
// Determine whether to use the Metal renderer on macOS
const char *metalEnv = SDL_getenv("MKXPZ_MACOS_METAL");
preferMetalRenderer = (!metalEnv || strcmp(metalEnv, "0")) && isMetalSupported();
#endif
}
static void setupScreenSize(Config &conf) {
if (conf.defScreenW <= 0)
conf.defScreenW = (conf.rgssVersion == 1 ? 640 : 544);
if (conf.defScreenH <= 0)
conf.defScreenH = (conf.rgssVersion == 1 ? 480 : 416);
}
void Config::readGameINI() {
if (!customScript.empty()) {
game.title = customScript.c_str();
if (rgssVersion == 0)
rgssVersion = 1;
setupScreenSize(*this);
return;
}
std::string iniFileName(execName + ".ini");
SDLRWStream iniFile(iniFileName.c_str(), "r");
bool convSuccess = false;
if (iniFile)
{
INIConfiguration ic;
if (ic.load(iniFile.stream()))
{
GUARD(game.title = ic.getStringProperty("Game", "Title"););
GUARD(game.scripts = ic.getStringProperty("Game", "Scripts"););
strReplace(game.scripts, '\\', '/');
if (game.title.empty()) {
Debug() << iniFileName + ": Could not find Game.Title";
}
if (game.scripts.empty())
Debug() << iniFileName + ": Could not find Game.Scripts";
}
}
else
Debug() << "Could not read" << iniFileName;
try {
game.title = Encoding::convertString(game.title);
convSuccess = true;
}
catch (const Exception &e) {
Debug() << iniFileName + ": Could not determine encoding of Game.Title";
}
if (game.title.empty() || !convSuccess)
game.title = "mkxp-z";
if (dataPathOrg.empty())
dataPathOrg = ".";
if (dataPathApp.empty())
dataPathApp = game.title;
customDataPath = prefPath(dataPathOrg.c_str(), dataPathApp.c_str());
commonDataPath = prefPath(".", "mkxp-z");
if (rgssVersion == 0) {
/* Try to guess RGSS version based on Data/Scripts extension */
rgssVersion = 1;
if (!game.scripts.empty()) {
const char *p = &game.scripts[game.scripts.size()];
const char *head = &game.scripts[0];
while (--p != head)
if (*p == '.')
break;
if (!strcmp(p, ".rvdata"))
rgssVersion = 2;
else if (!strcmp(p, ".rvdata2"))
rgssVersion = 3;
}
}
setupScreenSize(*this);
}