diff --git a/binding-mri/binding-mri-win32.h b/binding-mri/binding-mri-win32.h new file mode 100644 index 00000000..ae6a7f43 --- /dev/null +++ b/binding-mri/binding-mri-win32.h @@ -0,0 +1,43 @@ +#ifndef BINDING_MRI_WIN32_H +#define BINDING_MRI_WIN32_H + + +#include +#include "win-consoleutils.h" + +// Attempts to set $stdout and $stdin accordingly on Windows. Only +// called when debug mode is on, since that's when the console +// should be active. +void configureWindowsStreams() { + const int stdoutFD = getStdFD(STD_OUTPUT_HANDLE); + + // Configure $stdout + if (stdoutFD >= 0) { + VALUE winStdout = rb_funcall(rb_cIO, rb_intern("new"), 2, + INT2NUM(stdoutFD), rb_str_new_cstr("w+")); + + rb_gv_set("stdout", winStdout); + } + + const int stdinFD = getStdFD(STD_INPUT_HANDLE); + + // Configure $stdin + if (stdinFD >= 0) { + VALUE winStdin = rb_funcall(rb_cIO, rb_intern("new"), 2, + INT2NUM(stdinFD), rb_str_new_cstr("r")); + + rb_gv_set("stdin", winStdin); + } + + const int stderrFD = getStdFD(STD_ERROR_HANDLE); + + // Configure $stderr + if (stderrFD >= 0) { + VALUE winStderr = rb_funcall(rb_cIO, rb_intern("new"), 2, + INT2NUM(stderrFD), rb_str_new_cstr("w+")); + + rb_gv_set("stderr", winStderr); + } +} + +#endif \ No newline at end of file diff --git a/binding-mri/binding-mri.cpp b/binding-mri/binding-mri.cpp index 9b5cd89a..e90e51d7 100644 --- a/binding-mri/binding-mri.cpp +++ b/binding-mri/binding-mri.cpp @@ -34,6 +34,10 @@ #include "otherview-message.h" +#ifdef __WIN32 +#include "binding-mri-win32.h" +#endif + #include #include #undef inline @@ -175,6 +179,12 @@ static void mriBindingInit() " ENV['SSL_CERT_FILE'] = './lib/cacert.pem'\n" "end\n" ); + +#ifdef __WIN32 + if (shState->config().winConsole) + configureWindowsStreams(); +#endif + } static void @@ -604,6 +614,8 @@ static void showExc(VALUE exc, const BacktraceData &btData) static void mriBindingExecute() { + Config &conf = shState->rtData().config; + /* Normally only a ruby executable would do a sysinit, * but not doing it will lead to crashes due to closed * stdio streams on some platforms (eg. Windows) */ @@ -626,13 +638,53 @@ static void mriBindingExecute() // the three arguments are the executable name, and the '-e ""' is to tell ruby to run an empty file // otherwise (since this parses options for the ruby executable) it's gonna wait on stdin for code // --jit enables the jit i think - char options_argv1[] = "oneshot", options_argv2[] = "-e", options_argv3[] = ""; - char* options_argv[] = {options_argv1, options_argv2, options_argv3, NULL}; - ruby_options(3, options_argv); + std::vector rubyArgsC{"oneshot"}; + rubyArgsC.push_back("-e "); + void *node; + if (conf.mjitEnabled) { + std::string verboseLevel("--mjit-verbose="); verboseLevel += std::to_string(conf.mjitVerbosity); + std::string maxCache("--mjit-max-cache="); maxCache += std::to_string(conf.mjitMaxCache); + std::string minCalls("--mjit-min-calls="); minCalls += std::to_string(conf.mjitMinCalls); + rubyArgsC.push_back("--mjit"); + rubyArgsC.push_back(verboseLevel.c_str()); + rubyArgsC.push_back(maxCache.c_str()); + rubyArgsC.push_back(minCalls.c_str()); + } - rb_enc_set_default_external(rb_enc_from_encoding(rb_utf8_encoding())); + if (conf.yjitEnabled) { + std::string callThreshold("--yjit-call-threshold="); callThreshold += std::to_string(conf.yjitCallThreshold); + std::string maxVersions("--yjit-max-versions="); maxVersions += std::to_string(conf.yjitMaxVersions); + std::string greedyVersioning("--yjit-greedy-versioning"); greedyVersioning += std::to_string(conf.yjitGreedyVersioning); + rubyArgsC.push_back("--yjit"); + rubyArgsC.push_back(callThreshold.c_str()); + rubyArgsC.push_back(maxVersions.c_str()); + rubyArgsC.push_back(greedyVersioning.c_str()); + } - Config &conf = shState->rtData().config; + if (conf.jitEnabled) { + std::string verboseLevel("-jit-verbose="); verboseLevel += std::to_string(conf.jitVerbosity); + std::string maxCache("--jit-max-cache="); maxCache += std::to_string(conf.jitMaxCache); + std::string minCalls("--jit-min-calls="); minCalls += std::to_string(conf.jitMinCalls); + rubyArgsC.push_back("--jit"); + rubyArgsC.push_back(verboseLevel.c_str()); + rubyArgsC.push_back(maxCache.c_str()); + rubyArgsC.push_back(minCalls.c_str()); + } + + node = ruby_options(rubyArgsC.size(), const_cast(rubyArgsC.data())); + + int state; + bool valid = ruby_executable_node(node, &state); + if (valid) + state = ruby_exec_node(node); + if (state || !valid) { + showMsg("An error occurred while initializing Ruby. (Invalid JIT settings?)"); + ruby_cleanup(state); + shState->rtData().rqTermAck.set(); + return; + } + + rb_enc_set_default_external(rb_enc_from_encoding(rb_utf8_encoding())); if (!conf.rubyLoadpaths.empty()) { diff --git a/oneshot.conf.sample b/modshot.conf.sample similarity index 95% rename from oneshot.conf.sample rename to modshot.conf.sample index 0cb2b5e3..2fa37e5f 100644 --- a/oneshot.conf.sample +++ b/modshot.conf.sample @@ -9,13 +9,11 @@ # relative to gameFolder and ignoring both RTPs and # encrypted archives. - # Start the game in debug/developer mode # (default: disabled) # # debugMode=false - # Continuously print average FPS to console. # This setting does not affect the window title # FPS display toggled via F2 @@ -23,52 +21,44 @@ # # printFPS=false - # Start game in fullscreen mode, # i.e. Big Picture/console mode. # (default: disabled) # # fullscreen=false - # Preserve game screen aspect ratio, # as opposed to stretch-to-fill # (default: enabled) # # fixedAspectRatio=true - # Apply linear interpolation when game screen # is upscaled # (default: disabled) # # smoothScaling=false - # Sync screen redraws to the monitor refresh rate # (default: enabled) # # vsync=true - # Override the game window title # (default: none) # # windowTitle=Custom Title - # Enforce a static frame rate # (0 = disabled) # # fixedFramerate=0 - # Skip (don't draw) frames when behind # (default: enabled) # # frameSkip=true - # Use a fixed framerate that is approx. equal to the # native screen refresh rate. This is different from # "fixedFramerate" because the actual frame rate is @@ -79,13 +69,11 @@ # # syncToRefreshrate=false - # Don't use alpha blending when rendering text # (default: disabled) # # solidFonts=false - # Work around buggy graphics drivers which don't # properly synchronize texture access, most # apparent when text doesn't show up or the map @@ -94,7 +82,6 @@ # # subImageFix=false - # Enable framebuffer blitting if the driver is # capable of it. Some drivers carry buggy # implementations of this functionality, so @@ -103,7 +90,6 @@ # # enableBlitting=true - # Limit the maximum size (width, height) of # most textures mkxp will create (exceptions are # rendering backbuffers and similar). @@ -114,25 +100,21 @@ # # maxTextureSize=0 - # Set the base path of the game to '/path/to/game' # (default: executable directory) # # gameFolder=/path/to/game - # Allow symlinks for game assets to be followed # (default: disabled) # # allowSymlinks=false - # Set the game window icon to 'path/to/icon.png' # (default: none) # # iconPath=/path/to/icon.png - # Define raw scripts to be executed before the # actual Scripts.rxdata execution starts # (default: none) @@ -140,23 +122,12 @@ # preloadScript=my_win32_wrapper.rb # preloadScript=ruby18_fixes.rb - # Index all accesible assets via their lower case path # (emulates windows case insensitivity) # (default: enabled) # # pathCache=true - -# Add 'rtp1', 'rtp2.zip' and 'game.rgssad' to the -# asset search path (multiple allowed) -# (default: none) -# -# RTP=/path/to/rtp1 -# RTP=/path/to/rtp2.zip -# RTP=/path/to/game.rgssad - - # Font substitutions allow drop-in replacements of fonts # to be used without changing the RGSS scripts, # eg. providing 'Open Sans' when the game thinkgs it's @@ -171,7 +142,6 @@ # fontSub=Arial>Open Sans # fontSub=Times New Roman>Liberation Serif - # Because mkxp is usually distributed as a stand alone # build, no predefined load paths are initialized # ($:, $LOAD_PATH) in the MRI backend. With this option, @@ -183,7 +153,6 @@ # rubyLoadpath=/usr/lib64/ruby/ # rubyLoadpath=/usr/local/share/ruby/site_ruby - # Number of OpenAL sources to allocate for SE playback. # If there are a lot of sounds playing at the same time # and audibly cutting each other off, try increasing diff --git a/src/main.cpp b/src/main.cpp index 8f6f2da8..6f95501d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,6 +56,10 @@ #include "gamecontrollerdb.txt.xxd" #endif +#ifdef __WIN32 +#include "win-consoleutils.h" +#endif + static void rgssThreadError(RGSSThreadData *rtData, const std::string &msg) { @@ -297,6 +301,21 @@ int main(int argc, char *argv[]) { showInitError(std::string("Unable to switch into gameFolder ") + conf.gameFolder); return 0; } + +#ifdef __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 + extern int screenMain(Config &conf); if (conf.screenMode) diff --git a/src/meson.build b/src/meson.build index f0985f0c..1a8ccbc1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -111,6 +111,7 @@ main_source = files( 'thread/source/eventthread.cpp', 'thread/source/sharedstate.cpp', 'util/source/config.cpp', + 'util/source/win-consoleutils.cpp', 'main.cpp' ) diff --git a/src/util/headers/config.h b/src/util/headers/config.h index 315dcbee..2c30ff22 100644 --- a/src/util/headers/config.h +++ b/src/util/headers/config.h @@ -58,6 +58,49 @@ struct Config bool allowSymlinks; bool pathCache; + /* + MJIT options (experimental): + --mjit-warnings Enable printing JIT warnings + --mjit-debug Enable JIT debugging (very slow), or add cflags if specified + --mjit-wait Wait until JIT compilation finishes every time (for testing) + --mjit-save-temps + Save JIT temporary files in $TMP or /tmp (for testing) + --mjit-verbose=num + Print JIT logs of level num or less to stderr (default: 0) + --mjit-max-cache=num + Max number of methods to be JIT-ed in a cache (default: 10000) + --mjit-min-calls=num + Number of calls to trigger JIT (for testing, default: 10000) + + YJIT options (experimental): + --yjit-exec-mem-size=num + Size of executable memory block in MiB (default: 256) + --yjit-call-threshold + Number of calls to trigger JIT (default: 10) + --yjit-max-versions + Maximum number of versions per basic block (default: 4) + --yjit-greedy-versioning + Greedy versioning mode (default: disabled) + */ + + bool mjitEnabled; + int mjitVerbosity; + int mjitMaxCache; + int mjitMinCalls; + + bool yjitEnabled; + int yjitCallThreshold; + int yjitMaxVersions; + bool yjitGreedyVersioning; + + // This is for older versions of Ruby (3.0.* and below) + bool jitEnabled; + int jitVerbosity; + int jitMaxCache; + int jitMinCalls; + + bool winConsole; + std::string iconPath; struct diff --git a/src/util/headers/win-consoleutils.h b/src/util/headers/win-consoleutils.h new file mode 100644 index 00000000..e9cf75fa --- /dev/null +++ b/src/util/headers/win-consoleutils.h @@ -0,0 +1,15 @@ +#ifndef WIN_CONSOLEUTIL_H +#define WIN_CONSOLEUTIL_H + +#include +#include +#include +#include + +bool setupWindowsConsole(); + +void reopenWindowsStreams(); + +int getStdFD(const DWORD &nStdHandle); + +#endif diff --git a/src/util/source/config.cpp b/src/util/source/config.cpp index 5c154bac..d4d8838c 100644 --- a/src/util/source/config.cpp +++ b/src/util/source/config.cpp @@ -66,7 +66,7 @@ std::set setFromVec(const std::vector &vec) typedef std::vector StringVec; namespace po = boost::program_options; -#define CONF_FILE "oneshot.conf" +#define CONF_FILE "../modshot.conf" Config::Config() {} @@ -98,6 +98,19 @@ void Config::read(int argc, char *argv[]) PO_DESC(audioChannels, int, 30) \ PO_DESC(pathCache, bool, true) \ PO_DESC(isOtherView, bool, false) \ + PO_DESC(mjitEnabled, bool, false) \ + PO_DESC(mjitVerbosity, int, 0) \ + PO_DESC(mjitMaxCache, int, 100) \ + PO_DESC(mjitMinCalls, int, 10000) \ + PO_DESC(jitEnabled, bool, false) \ + PO_DESC(jitVerbosity, int, 0) \ + PO_DESC(jitMaxCache, int, 100) \ + PO_DESC(jitMinCalls, int, 10000) \ + PO_DESC(yjitEnabled, bool, false) \ + PO_DESC(yjitCallThreshold, int, 10) \ + PO_DESC(yjitMaxVersions, int, 4) \ + PO_DESC(yjitGreedyVersioning, bool, false) \ + PO_DESC(winConsole, bool, false) // Not gonna take your shit boost #define GUARD_ALL( exp ) try { exp } catch(...) {} diff --git a/src/util/source/win-consoleutils.cpp b/src/util/source/win-consoleutils.cpp new file mode 100644 index 00000000..6eb0c555 --- /dev/null +++ b/src/util/source/win-consoleutils.cpp @@ -0,0 +1,70 @@ +#if __WIN32__ + +#include "win-consoleutils.h" + +// Attempts to allocate a console and fetch the output handle. +// Returns whether the operation was successful. +// Extended error information can be received via GetLastError(). +bool setupWindowsConsole() +{ + if (!AllocConsole()) + return false; + + const HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + + return (handle != NULL && handle != INVALID_HANDLE_VALUE); +} + +static FILE *outStream; +static FILE *inStream; +static FILE *errStream; + +static int stdoutFD = -1; +static int stderrFD = -1; +static int stdinFD = -1; + +static int openStdHandle(const DWORD &nStdHandle); + +// Reopens the file streams. This should be done after successfully +// setting up the console. +void reopenWindowsStreams() +{ + freopen_s(&outStream, "CONOUT$", "w+", stdout); + freopen_s(&errStream, "CONOUT$", "w+", stderr); + freopen_s(&inStream, "CONIN$", "r", stdin); + std::cout.clear(); + std::clog.clear(); + std::cerr.clear(); + std::cin.clear(); + + stdoutFD = openStdHandle(STD_OUTPUT_HANDLE); + stdinFD = openStdHandle(STD_INPUT_HANDLE); + stderrFD = openStdHandle(STD_ERROR_HANDLE); +} + +int getStdFD(const DWORD &nStdHandle) +{ + switch (nStdHandle) + { + case STD_OUTPUT_HANDLE: + return stdoutFD; + case STD_INPUT_HANDLE: + return stdinFD; + case STD_ERROR_HANDLE: + return stderrFD; + default: + return -1; + } +} + +static int openStdHandle(const DWORD &nStdHandle) +{ + const HANDLE handle = GetStdHandle(nStdHandle); + + if (!handle || handle == INVALID_HANDLE_VALUE) + return -1; + + return _open_osfhandle((intptr_t)handle, _O_TEXT); +} + +#endif // __WIN32__ diff --git a/windows/shim.c b/windows/shim.c index 0c267305..d8b6b849 100644 --- a/windows/shim.c +++ b/windows/shim.c @@ -38,7 +38,7 @@ int WINAPI WinMain(HINSTANCE hInstance, argv[0] = ARGV0; } - _wexecv(L"lib\\oneshot.exe", argv); + _wexecv(L"lib\\modshot.exe", argv); MessageBoxW(NULL, L"Cannot start ModShot for some reason.\nPlease check your ModShot installation.", L"ModShot Shim",