mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-04-21 21:52:04 +02:00
551 lines
15 KiB
C++
551 lines
15 KiB
C++
/*
|
|
** main.cpp
|
|
**
|
|
** This file is part of mkxp.
|
|
**
|
|
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
|
|
**
|
|
** mkxp is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** mkxp is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef MKXPZ_BUILD_XCODE
|
|
#include "icon.png.xxd"
|
|
#endif
|
|
|
|
#include <alc.h>
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_image.h>
|
|
#include <SDL_sound.h>
|
|
#include <SDL_ttf.h>
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
#include <unistd.h>
|
|
#include <regex>
|
|
|
|
#include "binding.h"
|
|
#include "sharedstate.h"
|
|
#include "eventthread.h"
|
|
#include "util/debugwriter.h"
|
|
#include "util/exception.h"
|
|
#include "display/gl/gl-debug.h"
|
|
#include "display/gl/gl-fun.h"
|
|
|
|
#include "filesystem/filesystem.h"
|
|
|
|
#include "system/system.h"
|
|
|
|
#if defined(__WIN32__)
|
|
#include "resource.h"
|
|
#include <Winsock2.h>
|
|
#include "util/win-consoleutils.h"
|
|
|
|
// Try to work around buggy GL drivers that tend to be in Optimus laptops
|
|
// by forcing MKXP to use the dedicated card instead of the integrated one
|
|
#include <windows.h>
|
|
extern "C" {
|
|
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
|
|
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MKXPZ_STEAM
|
|
#include "steamshim_child.h"
|
|
#endif
|
|
|
|
#ifdef MKXPZ_BUILD_XCODE
|
|
#include <Availability.h>
|
|
#include "TouchBar.h"
|
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_15
|
|
#define MKXPZ_INIT_GL_LATER
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef MKXPZ_INIT_GL_LATER
|
|
#define GLINIT_SHOWERROR(s) showInitError(s)
|
|
#else
|
|
#define GLINIT_SHOWERROR(s) rgssThreadError(threadData, s)
|
|
#endif
|
|
|
|
static void rgssThreadError(RGSSThreadData *rtData, const std::string &msg);
|
|
static void showInitError(const std::string &msg);
|
|
|
|
static inline const char *glGetStringInt(GLenum name) {
|
|
return (const char *)gl.GetString(name);
|
|
}
|
|
|
|
static void printGLInfo() {
|
|
const std::string renderer(glGetStringInt(GL_RENDERER));
|
|
const std::string version(glGetStringInt(GL_VERSION));
|
|
std::regex rgx("ANGLE \\((.+), ANGLE Metal Renderer: (.+), Version (.+)\\)");
|
|
|
|
std::smatch matches;
|
|
if (std::regex_search(renderer, matches, rgx)) {
|
|
|
|
Debug() << "Backend :" << "Metal";
|
|
Debug() << "Metal Device :" << matches[2] << "(" + matches[1].str() + ")";
|
|
Debug() << "Renderer Version :" << matches[3].str();
|
|
|
|
std::smatch vmatches;
|
|
if (std::regex_search(version, vmatches, std::regex("\\(ANGLE (.+) git hash: .+\\)"))) {
|
|
Debug() << "ANGLE Version :" << vmatches[1].str();
|
|
}
|
|
return;
|
|
}
|
|
|
|
Debug() << "Backend :" << "OpenGL";
|
|
Debug() << "GL Vendor :" << glGetStringInt(GL_VENDOR);
|
|
Debug() << "GL Renderer :" << renderer;
|
|
Debug() << "GL Version :" << version;
|
|
Debug() << "GLSL Version :" << glGetStringInt(GL_SHADING_LANGUAGE_VERSION);
|
|
}
|
|
|
|
static SDL_GLContext initGL(SDL_Window *win, Config &conf,
|
|
RGSSThreadData *threadData);
|
|
|
|
int rgssThreadFun(void *userdata) {
|
|
RGSSThreadData *threadData = static_cast<RGSSThreadData *>(userdata);
|
|
|
|
#ifdef MKXPZ_INIT_GL_LATER
|
|
threadData->glContext =
|
|
initGL(threadData->window, threadData->config, threadData);
|
|
if (!threadData->glContext)
|
|
return 0;
|
|
#else
|
|
SDL_GL_MakeCurrent(threadData->window, threadData->glContext);
|
|
#endif
|
|
|
|
/* Setup AL context */
|
|
ALCcontext *alcCtx = alcCreateContext(threadData->alcDev, 0);
|
|
|
|
if (!alcCtx) {
|
|
rgssThreadError(threadData, "Error creating OpenAL context");
|
|
return 0;
|
|
}
|
|
|
|
alcMakeContextCurrent(alcCtx);
|
|
|
|
try {
|
|
SharedState::initInstance(threadData);
|
|
} catch (const Exception &exc) {
|
|
rgssThreadError(threadData, exc.msg);
|
|
alcDestroyContext(alcCtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Start script execution */
|
|
scriptBinding->execute();
|
|
|
|
threadData->rqTermAck.set();
|
|
threadData->ethread->requestTerminate();
|
|
|
|
SharedState::finiInstance();
|
|
|
|
alcDestroyContext(alcCtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void printRgssVersion(int ver) {
|
|
const char *const makers[] = {"", "XP", "VX", "VX Ace"};
|
|
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "RGSS version %d (RPG Maker %s)", ver,
|
|
makers[ver]);
|
|
|
|
Debug() << buf;
|
|
}
|
|
|
|
static void rgssThreadError(RGSSThreadData *rtData, const std::string &msg) {
|
|
rtData->rgssErrorMsg = msg;
|
|
rtData->ethread->requestTerminate();
|
|
rtData->rqTermAck.set();
|
|
}
|
|
|
|
static void showInitError(const std::string &msg) {
|
|
Debug() << msg;
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "mkxp-z", msg.c_str(), 0);
|
|
}
|
|
|
|
static void setupWindowIcon(const Config &conf, SDL_Window *win) {
|
|
SDL_RWops *iconSrc;
|
|
|
|
if (conf.iconPath.empty())
|
|
#ifndef MKXPZ_BUILD_XCODE
|
|
iconSrc = SDL_RWFromConstMem(___assets_icon_png, ___assets_icon_png_len);
|
|
#else
|
|
iconSrc = SDL_RWFromFile(mkxp_fs::getPathForAsset("icon", "png").c_str(), "rb");
|
|
#endif
|
|
else
|
|
iconSrc = SDL_RWFromFile(conf.iconPath.c_str(), "rb");
|
|
|
|
SDL_Surface *iconImg = IMG_Load_RW(iconSrc, SDL_TRUE);
|
|
|
|
if (iconImg) {
|
|
SDL_SetWindowIcon(win, iconImg);
|
|
SDL_FreeSurface(iconImg);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
|
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
|
|
|
|
#ifdef GLES2_HEADER
|
|
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
|
|
#endif
|
|
|
|
/* initialize SDL first */
|
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
|
|
showInitError(std::string("Error initializing SDL: ") + SDL_GetError());
|
|
return 0;
|
|
}
|
|
|
|
if (!EventThread::allocUserEvents()) {
|
|
showInitError("Error allocating SDL user events");
|
|
return 0;
|
|
}
|
|
|
|
#ifndef WORKDIR_CURRENT
|
|
char dataDir[512]{};
|
|
#if defined(__linux__)
|
|
char *tmp{};
|
|
tmp = getenv("SRCDIR");
|
|
if (tmp) {
|
|
strncpy(dataDir, tmp, sizeof(dataDir));
|
|
}
|
|
#endif
|
|
if (!dataDir[0]) {
|
|
strncpy(dataDir, mkxp_fs::getDefaultGameRoot().c_str(), sizeof(dataDir));
|
|
}
|
|
mkxp_fs::setCurrentDirectory(dataDir);
|
|
#endif
|
|
|
|
/* now we load the config */
|
|
Config conf;
|
|
conf.read(argc, argv);
|
|
|
|
#if defined(__WIN32__)
|
|
// Create a debug console in debug mode
|
|
if (conf.winConsole) {
|
|
if (setupWindowsConsole()) {
|
|
reopenWindowsStreams();
|
|
} else {
|
|
char buf[200];
|
|
snprintf(buf, sizeof(buf), "Error allocating console: %lu",
|
|
GetLastError());
|
|
showInitError(std::string(buf));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef MKXPZ_STEAM
|
|
if (!STEAMSHIM_init()) {
|
|
showInitError("Failed to initialize Steamworks. The application cannot "
|
|
"continue launching.");
|
|
SDL_Quit();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
if (conf.windowTitle.empty())
|
|
conf.windowTitle = conf.game.title;
|
|
|
|
assert(conf.rgssVersion >= 1 && conf.rgssVersion <= 3);
|
|
printRgssVersion(conf.rgssVersion);
|
|
|
|
int imgFlags = IMG_INIT_PNG | IMG_INIT_JPG;
|
|
if (IMG_Init(imgFlags) != imgFlags) {
|
|
showInitError(std::string("Error initializing SDL_image: ") +
|
|
SDL_GetError());
|
|
SDL_Quit();
|
|
|
|
#ifdef MKXPZ_STEAM
|
|
STEAMSHIM_deinit();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (TTF_Init() < 0) {
|
|
showInitError(std::string("Error initializing SDL_ttf: ") +
|
|
SDL_GetError());
|
|
IMG_Quit();
|
|
SDL_Quit();
|
|
|
|
#ifdef MKXPZ_STEAM
|
|
STEAMSHIM_deinit();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (Sound_Init() == 0) {
|
|
showInitError(std::string("Error initializing SDL_sound: ") +
|
|
Sound_GetError());
|
|
TTF_Quit();
|
|
IMG_Quit();
|
|
SDL_Quit();
|
|
|
|
#ifdef MKXPZ_STEAM
|
|
STEAMSHIM_deinit();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
#if defined(__WIN32__)
|
|
WSAData wsadata = {0};
|
|
if (WSAStartup(0x101, &wsadata) || wsadata.wVersion != 0x101) {
|
|
char buf[200];
|
|
snprintf(buf, sizeof(buf), "Error initializing winsock: %08X",
|
|
WSAGetLastError());
|
|
showInitError(
|
|
std::string(buf)); // Not an error worth ending the program over
|
|
}
|
|
#endif
|
|
|
|
SDL_Window *win;
|
|
Uint32 winFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_ALLOW_HIGHDPI;
|
|
|
|
if (conf.winResizable)
|
|
winFlags |= SDL_WINDOW_RESIZABLE;
|
|
if (conf.fullscreen)
|
|
winFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
|
|
#ifdef GLES2_HEADER
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
|
|
|
// LoadLibrary properly initializes EGL, it won't work otherwise.
|
|
// Doesn't completely do it though, needs a small patch to SDL
|
|
#ifdef MKXPZ_BUILD_XCODE
|
|
SDL_setenv("ANGLE_DEFAULT_PLATFORM", (conf.preferMetalRenderer) ? "metal" : "opengl", true);
|
|
SDL_GL_LoadLibrary("@rpath/libEGL.dylib");
|
|
#endif
|
|
#endif
|
|
|
|
win = SDL_CreateWindow(conf.windowTitle.c_str(), SDL_WINDOWPOS_UNDEFINED,
|
|
SDL_WINDOWPOS_UNDEFINED, conf.defScreenW,
|
|
conf.defScreenH, winFlags);
|
|
|
|
if (!win) {
|
|
showInitError(std::string("Error creating window: ") + SDL_GetError());
|
|
|
|
#ifdef MKXPZ_STEAM
|
|
STEAMSHIM_deinit();
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#ifdef MKXPZ_BUILD_XCODE
|
|
{
|
|
std::string downloadsPath = "/Users/" + mkxp_sys::getUserName() + "/Downloads";
|
|
|
|
if (mkxp_fs::getCurrentDirectory().find(downloadsPath) == 0) {
|
|
showInitError(conf.game.title +
|
|
" cannot run from the Downloads directory.\n\n" +
|
|
"Please move the application to the Applications folder (or anywhere else) " +
|
|
"and try again.");
|
|
#ifdef MKXPZ_STEAM
|
|
STEAMSHIM_deinit();
|
|
#endif
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(MKXPZ_BUILD_XCODE)
|
|
#define DEBUG_FSELECT_MSG "Select the folder from which to load game files. This is the folder containing the game's INI."
|
|
#define DEBUG_FSELECT_PROMPT "Load Game"
|
|
if (conf.manualFolderSelect) {
|
|
std::string dataDirStr = mkxp_fs::selectPath(win, DEBUG_FSELECT_MSG, DEBUG_FSELECT_PROMPT);
|
|
if (!dataDirStr.empty()) {
|
|
conf.gameFolder = dataDirStr;
|
|
mkxp_fs::setCurrentDirectory(dataDirStr.c_str());
|
|
Debug() << "Current directory set to" << dataDirStr;
|
|
conf.read(argc, argv);
|
|
conf.readGameINI();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* OSX and Windows have their own native ways of
|
|
* dealing with icons; don't interfere with them */
|
|
#ifdef __LINUX__
|
|
setupWindowIcon(conf, win);
|
|
#else
|
|
(void)setupWindowIcon;
|
|
#endif
|
|
|
|
ALCdevice *alcDev = alcOpenDevice(0);
|
|
|
|
if (!alcDev) {
|
|
showInitError("Could not detect an available audio device.");
|
|
SDL_DestroyWindow(win);
|
|
TTF_Quit();
|
|
IMG_Quit();
|
|
SDL_Quit();
|
|
|
|
#ifdef MKXPZ_STEAM
|
|
STEAMSHIM_deinit();
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
SDL_DisplayMode mode;
|
|
SDL_GetDisplayMode(0, 0, &mode);
|
|
|
|
/* Can't sync to display refresh rate if its value is unknown */
|
|
if (!mode.refresh_rate)
|
|
conf.syncToRefreshrate = false;
|
|
|
|
EventThread eventThread;
|
|
|
|
#ifndef MKXPZ_INIT_GL_LATER
|
|
SDL_GLContext glCtx = initGL(win, conf, 0);
|
|
#else
|
|
SDL_GLContext glCtx = NULL;
|
|
#endif
|
|
|
|
RGSSThreadData rtData(&eventThread, argv[0], win, alcDev, mode.refresh_rate,
|
|
mkxp_sys::getScalingFactor(), conf, glCtx);
|
|
|
|
int winW, winH, drwW, drwH;
|
|
SDL_GetWindowSize(win, &winW, &winH);
|
|
rtData.windowSizeMsg.post(Vec2i(winW, winH));
|
|
|
|
SDL_GL_GetDrawableSize(win, &drwW, &drwH);
|
|
rtData.drawableSizeMsg.post(Vec2i(drwW, drwH));
|
|
|
|
/* Load and post key bindings */
|
|
rtData.bindingUpdateMsg.post(loadBindings(conf));
|
|
|
|
#ifdef MKXPZ_BUILD_XCODE
|
|
// Create Touch Bar
|
|
initTouchBar(win, conf);
|
|
#endif
|
|
|
|
/* Start RGSS thread */
|
|
SDL_Thread *rgssThread = SDL_CreateThread(rgssThreadFun, "rgss", &rtData);
|
|
|
|
/* Start event processing */
|
|
eventThread.process(rtData);
|
|
|
|
/* Request RGSS thread to stop */
|
|
rtData.rqTerm.set();
|
|
|
|
/* Wait for RGSS thread response */
|
|
for (int i = 0; i < 1000; ++i) {
|
|
/* We can stop waiting when the request was ack'd */
|
|
if (rtData.rqTermAck) {
|
|
Debug() << "RGSS thread ack'd request after" << i * 10 << "ms";
|
|
break;
|
|
}
|
|
|
|
/* Give RGSS thread some time to respond */
|
|
SDL_Delay(10);
|
|
}
|
|
|
|
/* If RGSS thread ack'd request, wait for it to shutdown,
|
|
* otherwise abandon hope and just end the process as is. */
|
|
if (rtData.rqTermAck)
|
|
SDL_WaitThread(rgssThread, 0);
|
|
else
|
|
SDL_ShowSimpleMessageBox(
|
|
SDL_MESSAGEBOX_ERROR, conf.game.title.c_str(),
|
|
std::string("The RGSS script seems to be stuck. "+conf.game.title+" will now force quit.").c_str(),
|
|
win);
|
|
|
|
if (!rtData.rgssErrorMsg.empty()) {
|
|
Debug() << rtData.rgssErrorMsg;
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, conf.game.title.c_str(),
|
|
rtData.rgssErrorMsg.c_str(), win);
|
|
}
|
|
|
|
if (rtData.glContext)
|
|
SDL_GL_DeleteContext(rtData.glContext);
|
|
|
|
/* Clean up any remainin events */
|
|
eventThread.cleanup();
|
|
|
|
Debug() << "Shutting down.";
|
|
|
|
alcCloseDevice(alcDev);
|
|
SDL_DestroyWindow(win);
|
|
|
|
#if defined(__WIN32__)
|
|
if (wsadata.wVersion)
|
|
WSACleanup();
|
|
#endif
|
|
|
|
#ifdef MKXPZ_STEAM
|
|
STEAMSHIM_deinit();
|
|
#endif
|
|
Sound_Quit();
|
|
TTF_Quit();
|
|
IMG_Quit();
|
|
SDL_Quit();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SDL_GLContext initGL(SDL_Window *win, Config &conf,
|
|
RGSSThreadData *threadData) {
|
|
SDL_GLContext glCtx{};
|
|
|
|
/* Setup GL context. Must be done in main thread since macOS 10.15 */
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
|
|
if (conf.debugMode)
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
|
|
|
|
glCtx = SDL_GL_CreateContext(win);
|
|
|
|
if (!glCtx) {
|
|
GLINIT_SHOWERROR(std::string("Could not create OpenGL context: ") + SDL_GetError());
|
|
return 0;
|
|
}
|
|
|
|
try {
|
|
initGLFunctions();
|
|
} catch (const Exception &exc) {
|
|
GLINIT_SHOWERROR(exc.msg);
|
|
SDL_GL_DeleteContext(glCtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// This breaks scaling for Retina screens.
|
|
// Using Metal should be rendering this irrelevant anyway, hopefully
|
|
#ifndef MKXPZ_BUILD_XCODE
|
|
if (!conf.enableBlitting)
|
|
gl.BlitFramebuffer = 0;
|
|
#endif
|
|
|
|
gl.ClearColor(0, 0, 0, 1);
|
|
gl.Clear(GL_COLOR_BUFFER_BIT);
|
|
SDL_GL_SwapWindow(win);
|
|
|
|
printGLInfo();
|
|
|
|
bool vsync = conf.vsync || conf.syncToRefreshrate;
|
|
SDL_GL_SetSwapInterval(vsync ? 1 : 0);
|
|
|
|
// GLDebugLogger dLogger;
|
|
return glCtx;
|
|
}
|