mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-04-22 06:02:04 +02:00
354 lines
10 KiB
C++
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);
|
|
}
|