This commit is contained in:
DepressedTWM 2026-07-27 16:09:52 +04:00
parent 0cc72b0b4e
commit 501ff1b02b
13 changed files with 97 additions and 152 deletions

1
MODDING.md Normal file
View file

@ -0,0 +1 @@
# How Sunshine ModLoader work

View file

@ -32,6 +32,7 @@
#include "boost-hash.h" #include "boost-hash.h"
#include "meow.h" #include "meow.h"
#include "sunshine.h" #include "sunshine.h"
#include "modloader.h"
#include <ruby.h> #include <ruby.h>
#include <ruby/debug.h> #include <ruby/debug.h>
@ -178,6 +179,7 @@ static void mriBindingInit(){
SunshineBindingInit(); SunshineBindingInit();
steamBindingInit(); steamBindingInit();
shaderBindingInit(); shaderBindingInit();
ModLoader();
ModLoaderBindingInit(); ModLoaderBindingInit();
keybindingsBindingInit(); keybindingsBindingInit();
lightmapBindingInit(); lightmapBindingInit();
@ -198,6 +200,7 @@ static void mriBindingInit(){
_rb_define_module_function(mod, "puts", mkxpPuts); _rb_define_module_function(mod, "puts", mkxpPuts);
_rb_define_module_function(mod, "raw_key_states", mkxpRawKeyStates); _rb_define_module_function(mod, "raw_key_states", mkxpRawKeyStates);
_rb_define_module_function(mod, "mouse_in_window", mkxpMouseInWindow); _rb_define_module_function(mod, "mouse_in_window", mkxpMouseInWindow);
} }
static void printP(int argc, VALUE *argv, const char *convMethod, const char *sep){ static void printP(int argc, VALUE *argv, const char *convMethod, const char *sep){

View file

@ -186,8 +186,7 @@ RB_METHOD(_marshalLoad){
VALUE marsh = rb_const_get(rb_cObject, rb_intern("Marshal")); VALUE marsh = rb_const_get(rb_cObject, rb_intern("Marshal"));
VALUE v[] = { port, utf8Proc }; VALUE v[] = { port, utf8Proc };
return rb_marshal_load(port); // uh.. well.. it works now at least. return rb_marshal_load(port);
//return rb_funcall_with_block(marsh, rb_intern("_mkxp_load_alias"), 1, &port, utf8Proc);
} }
void fileIntBindingInit(){ void fileIntBindingInit(){

View file

@ -2,6 +2,7 @@
#include <SDL3/SDL_version.h> #include <SDL3/SDL_version.h>
#include <SDL3/SDL_gamepad.h> #include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_joystick.h> #include <SDL3/SDL_joystick.h>
#include <SDL3/SDL_hints.h>
#include <limits.h> #include <limits.h>
#include "ruby/backward/cxxanyargs.hpp" #include "ruby/backward/cxxanyargs.hpp"
#include "ruby/internal/arithmetic/double.h" #include "ruby/internal/arithmetic/double.h"
@ -21,6 +22,10 @@ static VALUE sunshine_set_crash_privacy(VALUE, VALUE v) {
return v; return v;
} }
static VALUE sunshine_set_hint(VALUE, VALUE h, VALUE v) {
return SDL_SetHint(StringValueCStr(h), StringValueCStr(v));
}
static VALUE obj_clone(VALUE self){ static VALUE obj_clone(VALUE self){
return rb_obj_clone(self); return rb_obj_clone(self);
} }
@ -57,6 +62,7 @@ void SunshineBindingInit(){
rb_const_set(module, rb_intern("SECURITYSTATE"), rb_str_new_cstr(securitystate)); rb_const_set(module, rb_intern("SECURITYSTATE"), rb_str_new_cstr(securitystate));
rb_define_singleton_method(module, "crashprivacy", RUBY_METHOD_FUNC(sunshine_get_crash_privacy), 0); rb_define_singleton_method(module, "crashprivacy", RUBY_METHOD_FUNC(sunshine_get_crash_privacy), 0);
rb_define_singleton_method(module, "crashprivacy=", RUBY_METHOD_FUNC(sunshine_set_crash_privacy), 1); rb_define_singleton_method(module, "crashprivacy=", RUBY_METHOD_FUNC(sunshine_set_crash_privacy), 1);
rb_define_singleton_method(module, "setSDLHint", RUBY_METHOD_FUNC(sunshine_set_hint), 2);
//если методы доступны то просто не перезаписываем их //если методы доступны то просто не перезаписываем их
if (!rb_respond_to(rb_cObject, rb_intern("class"))) { if (!rb_respond_to(rb_cObject, rb_intern("class"))) {
rb_define_method(rb_cObject, "class", rb_obj_class, 0); rb_define_method(rb_cObject, "class", rb_obj_class, 0);

View file

@ -10,3 +10,4 @@ Added help screen in Settings
"Skip Text (R)" fixed "Skip Text (R)" fixed
Fixed language selection Fixed language selection
Replaced Japanese font with pixel one Replaced Japanese font with pixel one
Added SDL_HINT_INVALID_PARAM_CHECKS controller in Settings>Advanced

View file

@ -86,7 +86,8 @@ set(scripts
Input.rb Input.rb
Dynamic_Light.rb Dynamic_Light.rb
Gamepad_Colors.rb Gamepad_Colors.rb
Dynamic_Resolution.rb) Dynamic_Resolution.rb
ModAPI.rb)
find_program(RUBY_EXE ruby find_program(RUBY_EXE ruby
DOC "Location of the Ruby executable") DOC "Location of the Ruby executable")

View file

@ -32,6 +32,8 @@ module Settings
# Advanced # Advanced
:crashlog_privacy => false, :crashlog_privacy => false,
:streamer_privacy => false,
:SDL_HINT_INVALID_PARAM_CHECKS => false,
#controls #controls
:gamepad_led => true, :gamepad_led => true,
@ -44,7 +46,7 @@ module Settings
:debug_text_scene_title => true, :debug_text_scene_title => true,
:debug_picture_names => false, :debug_picture_names => false,
:debug_lightmap => false, :debug_lightmap => false,
:SDL_HINT_SHUTDOWN_DBUS_ON_QUIT => false,
#Hidden #Hidden
:is_dejavu => false, :is_dejavu => false,
@ -125,6 +127,12 @@ end
class Window_Settings class Window_Settings
# if parameter is linked to data in class Settings, then default value in this structure is ignored # if parameter is linked to data in class Settings, then default value in this structure is ignored
DATA = { DATA = {
"Help" => [
{ :type => :base, :name => "(!) - It's Dangerous!" },
{ :type => :base, :name => "(WIP) - Work In Progress" },
{ :type => :base, :name => "(GRR) - Game Restart Required" },
{ :type => :base, :name => "(LRR) - Location Reload Required" },
],
"Audio" => [ "Audio" => [
{ {
:type => :slider, :type => :slider,
@ -140,6 +148,7 @@ class Window_Settings
:min => 0, :min => 0,
:max => 100 :max => 100
}, },
{ :type => :sep, :name => "Game tracks"},
{ {
:type => :bool, :type => :bool,
:name => "ITS TIME FOR FIGHT CRIME", :name => "ITS TIME FOR FIGHT CRIME",
@ -165,13 +174,13 @@ class Window_Settings
:name => "Frameskip", :name => "Frameskip",
:parameter => :frameskip :parameter => :frameskip
}, },
{ :type => :sep, :name => "Other"},
{ {
:type => :enum, :type => :enum,
:name => "Scaling mode", :name => "Scaling mode",
:parameter => :scaling_mode, :parameter => :scaling_mode,
:values => ["Nearest Neighbor", "Smooth(old)"] :values => ["Nearest Neighbor", "Smooth(old)"]
}, },
{ :type => :sep, :name => "Effects"},
{ {
:type => :bool, :type => :bool,
:name => "Dynamic Light", :name => "Dynamic Light",
@ -182,11 +191,6 @@ class Window_Settings
:name => "World machine shader", :name => "World machine shader",
:parameter => :twm_shader :parameter => :twm_shader
}, },
{
:type => :bool,
:name => "Colorblind mode",
:parameter => :colorblind
},
], ],
"UI" => [ "UI" => [
{ {
@ -208,6 +212,12 @@ class Window_Settings
}, },
], ],
"Gameplay" => [ "Gameplay" => [
{ :type => :sep, :name => "Vanila" },
{
:type => :bool,
:name => "Colorblind mode",
:parameter => :colorblind
},
{ {
:type => :enum, :type => :enum,
:name => "Default movement", :name => "Default movement",
@ -219,8 +229,9 @@ class Window_Settings
:name => "Skip Text (R)", :name => "Skip Text (R)",
:parameter => :skip_text :parameter => :skip_text
}, },
{ :type => :sep, :name => "Sunshine" },
{ {
:type => :bool, :type => :bool,
:name => "Purple message box for Entity", :name => "Purple message box for Entity",
:parameter => :en_purple_messagebox :parameter => :en_purple_messagebox
}, },
@ -241,7 +252,7 @@ class Window_Settings
}, },
{ {
:type => :bool, :type => :bool,
:name => "World machine shader on Entity footprints", :name => "World machine shader on Entity footprints(WIP)",
:parameter => :twm_shader_footprint :parameter => :twm_shader_footprint
}, },
], ],
@ -325,7 +336,6 @@ class Window_Settings
:parameter => :controls_nav_right, :parameter => :controls_nav_right,
:bind => Input::R :bind => Input::R
}, },
#{ :type => :sep, :name => "Other" },
{ {
:type => :action, :type => :action,
:name => "Reset Controls", :name => "Reset Controls",
@ -333,11 +343,22 @@ class Window_Settings
}, },
], ],
"Advanced" => [ "Advanced" => [
{ :type => :sep, :name => "Privacy" },
{ {
:type => :bool, :type => :bool,
:name => "Crashlog privacy", :name => "Crashlog privacy",
:parameter => :crashlog_privacy :parameter => :crashlog_privacy
},
{
:type => :bool,
:name => "Streamer privacy",
:parameter => :streamer_privacy
},
{ :type => :sep, :name => "Other" },
{
:type => :bool,
:name => "SDL_HINT_INVALID_PARAM_CHECKS (!)",
:parameter => :SDL_HINT_INVALID_PARAM_CHECKS
}, },
], ],
"Debug" => [ "Debug" => [
@ -371,6 +392,11 @@ class Window_Settings
:name => "Debug lightmap", :name => "Debug lightmap",
:parameter => :debug_lightmap :parameter => :debug_lightmap
}, },
{
:type => :bool,
:name => "SDL_HINT_SHUTDOWN_DBUS_ON_QUIT",
:parameter => :SDL_HINT_SHUTDOWN_DBUS_ON_QUIT
},
{ :type => :sep }, { :type => :sep },
{ {
:type => :key, :type => :key,
@ -378,12 +404,20 @@ class Window_Settings
:parameter => :controls_debug, :parameter => :controls_debug,
:bind => Input::DEBUGACTION :bind => Input::DEBUGACTION
}, },
{ :type => :sep }, { :type => :seps},
{ {
:type => :action, :type => :action,
:name => "Clear image cache", :name => "Clear image cache",
:action => Proc.new { RPG::Cache.clear } :action => Proc.new { RPG::Cache.clear }
} }
], ],
"Mods" => [
{ :type => :base, :name => "Modloader enabled? #{ModLoader::IS_ENABLED}" },
],
} }
# Part of ModAPI
def self.add_setting(setting)
DATA["Mods"] << setting
end
end end

View file

@ -62,13 +62,28 @@ module Settings
$game_switches[251] = value != 0 $game_switches[251] = value != 0
end end
def skip_text(value) def skip_text(value)
# idk how to set skip_text parameter $game_switches[253] = value != 0
# TODO: fix skip_text settings setter
end end
def frameskip(value) def frameskip(value)
Graphics.frameskip = value Graphics.frameskip = value
end end
def SDL_HINT_INVALID_PARAM_CHECKS(value)
if value
Sunshine.setSDLHint("SDL_HINT_INVALID_PARAM_CHECKS", "1")
else
Sunshine.setSDLHint("SDL_HINT_INVALID_PARAM_CHECKS", "2")
end
end
def SDL_HINT_SHUTDOWN_DBUS_ON_QUIT(value)
if value
Sunshine.setSDLHint("SDL_HINT_SHUTDOWN_DBUS_ON_QUIT", "1")
else
Sunshine.setSDLHint("SDL_HINT_SHUTDOWN_DBUS_ON_QUIT", "0")
end
end
end end
end end
end end

3
scripts/ModAPI.rb Normal file
View file

@ -0,0 +1,3 @@
module ModAPI
APIVER = 1
end

View file

@ -108,3 +108,6 @@ Particles
EdText EdText
Main Main
ModAPI

View file

@ -51,10 +51,6 @@
#include "i18n.h" #include "i18n.h"
#include "security.h" #include "security.h"
#ifndef ps2
#include "modloader.h"
#endif
#include "sunshine.h" #include "sunshine.h"
#include "define.h" #include "define.h"
@ -303,16 +299,6 @@ int main(int argc, char *argv[]){
} }
} }
#ifndef ps2
std::string new_path = ModLoader(conf);
if(new_path != ""){
if (chdir(new_path.c_str()) != 0){
crash(Exception::MEOW, "Unable to switch into new gameFolder %s", new_path);
return 0;
}
}
#endif
extern int screenMain(Config &conf); extern int screenMain(Config &conf);
if (conf.screenMode) if (conf.screenMode)
return screenMain(conf); return screenMain(conf);

View file

@ -25,36 +25,9 @@
#include <algorithm> #include <algorithm>
#include <system_error> #include <system_error>
#include <SDL3/SDL_system.h> #include <SDL3/SDL_system.h>
#include <physfs.h>
namespace fs = std::filesystem; namespace fs = std::filesystem;
// Get directory for storing cached builds
//TODO: support for other OS and platforms
std::string getCacheDir(){
#ifdef _WIN32
return SDL_getenv("Temp");
#elif __linux__
return std::string(SDL_getenv("HOME")) + "/.cache";
#elif BSD
return std::string(SDL_getenv("HOME")) + "/.cache";
#elif __APPLE__
return "~/Library/Caches";
#elif __ANDROID__
return std::string str(SDL_GetAndroidCachePath());
#else
return "idk";
#endif
}
//helper
bool ensure_parent_dir(const std::filesystem::path& p){
if (p.has_parent_path()){
std::error_code ec;
std::filesystem::create_directories(p.parent_path(), ec);
return !ec;
}
return true;
}
std::string sha512(const std::string str){ std::string sha512(const std::string str){
unsigned char hash[SHA512_DIGEST_LENGTH]; unsigned char hash[SHA512_DIGEST_LENGTH];
@ -98,19 +71,14 @@ std::string sha256_file(const std::string &fn) {
return out; return out;
} }
#ifdef __EMSCRIPTEN__ void ModLoader(){
std::string ModLoader(Config conf){ return ""; } std::string path = "mods";
#else
std::string ModLoader(Config conf){
std::string path = conf.Modloader.ModsDirPath;
if (!fs::exists(path) || !std::filesystem::is_directory(path)) { if (!fs::exists(path) || !std::filesystem::is_directory(path)) {
Debug() << "[MODLOADER] Mods directory not found, skip."; Debug() << "[MODLOADER] Mods directory not found, skip.";
return "";
} }
if (fs::is_empty(path)){ if (fs::is_empty(path)){
Debug() << "[MODLOADER] Mods directory empty, skip."; Debug() << "[MODLOADER] Mods directory empty, skip.";
return "";
} }
//buildID - unique ID of a certain combination of mods //buildID - unique ID of a certain combination of mods
@ -118,16 +86,18 @@ std::string ModLoader(Config conf){
std::string buildID_tmp = ""; std::string buildID_tmp = "";
std::vector<std::string> mod_list = {}; std::vector<std::string> mod_list = {};
try{ try{
//1.check if any zip(mod) file, 2. calculate sha256 hash of zip(mod) files //1.check if any zip(mod) file, 2. calculate sha256 hash of zip(mod) files 3.mount mod via PhysFS
for (const auto &entry : std::filesystem::directory_iterator(path, std::filesystem::directory_options::skip_permission_denied)) { for (const auto &entry : std::filesystem::directory_iterator(path, std::filesystem::directory_options::skip_permission_denied)) {
std::error_code ec; std::error_code ec;
auto p = entry.path(); auto p = entry.path();
if (!std::filesystem::is_regular_file(p, ec) || ec) continue; if (!std::filesystem::is_regular_file(p, ec) || ec) continue;
auto ext = p.extension().string(); auto ext = p.extension().string();
if (ext != ".zip") continue; if (ext != ".zip") continue;
std::string full = p.string(); std::string full = p.string();
int ok = PHYSFS_mount(full.c_str(), "/", 0);
if (!ok) {
crash(Exception::ModLoaderError, "PhysFS_mount failed: %s", PHYSFS_getLastError());
}
Debug() << "[MODLOADER] " << full; Debug() << "[MODLOADER] " << full;
mod_list.push_back(full); mod_list.push_back(full);
mods_count++; mods_count++;
@ -138,84 +108,9 @@ std::string ModLoader(Config conf){
buildID = sha512(buildID_tmp); buildID = sha512(buildID_tmp);
Debug() << "[MODLOADER] BuildID: " << buildID; Debug() << "[MODLOADER] BuildID: " << buildID;
std::string path3 = getCacheDir() + "/sunshine-" + buildID;
int err = 0;
modloader_is_enabled = true; modloader_is_enabled = true;
if(!std::filesystem::exists(path3)){
std::error_code ec;
fs::create_directory(path3, ec);
if (ec) {
crash(Exception::ModLoaderError, "Failed to create destination: %s", ec.message().c_str());
}
fs::copy(conf.gameFolder, path3, fs::copy_options::recursive | fs::copy_options::overwrite_existing, ec);
if (ec) {
crash(Exception::ModLoaderError, "Copy error: %s", ec.message().c_str());
}
//Extracting zipsodpsofspo idk
for (size_t i = 0; i < mod_list.size(); ++i){
zip_t* za = zip_open(mod_list[i].c_str(), ZIP_RDONLY, &err);
if (!za){
crash(Exception::ModLoaderError, "Failed to open zip");
}
zip_int64_t n = zip_get_num_entries(za, 0);
for (zip_uint64_t i2 = 0; i2 < static_cast<zip_uint64_t>(n); ++i2) {
zip_stat_t st;
if (zip_stat_index(za, i2, 0, &st) != 0) {
crash(Exception::ModLoaderError, "zip_stat_index failed");
}
std::string name = st.name;
std::filesystem::path target = path3 / std::filesystem::path(name);
// If entry name ends with '/', treat as directory
if (!name.empty() && name.back() == '/') {
std::error_code ec;
std::filesystem::create_directories(target, ec);
if (ec) crash(Exception::MEOW, "Failed to create dir");
continue;
}
if (!ensure_parent_dir(target)){
crash(Exception::ModLoaderError, "Failed to create parent dirs");
}
zip_file_t* zf = zip_fopen_index(za, i2, 0);
if (!zf) {
crash(Exception::ModLoaderError, "zip_fopen_index failed");
}
std::ofstream out(target, std::ios::binary);
if (!out) {
crash(Exception::ModLoaderError, "Failed to open output file %s", target.c_str());
zip_fclose(zf);
}
const zip_uint64_t bufsize = 4096;
std::vector<char> buf(bufsize);
zip_int64_t bytes_read;
zip_uint64_t remaining = st.size;
while (remaining > 0) {
zip_uint64_t to_read = std::min<zip_uint64_t>(bufsize, remaining);
bytes_read = zip_fread(zf, buf.data(), to_read);
if (bytes_read < 0) {
crash(Exception::ModLoaderError, "zip_fread error for %s", name.c_str());
break;
}
out.write(buf.data(), bytes_read);
remaining -= bytes_read;
}
zip_fclose(zf);
}
zip_close(za);
}
return path3;
}else{
return path3;
}
}catch(const std::exception& e){ }catch(const std::exception& e){
crash(Exception::ModLoaderError, "Something is wrong, Exception: %s ", e.what()); crash(Exception::ModLoaderError, "Something is wrong, Exception: %s ", e.what());
} }
} }
#endif

View file

@ -1,7 +1,5 @@
//modloader bruh //modloader bruh
#include <string> void ModLoader();
#include "config.h"
std::string ModLoader(Config conf);
inline bool modloader_is_enabled = false; inline bool modloader_is_enabled = false;
inline unsigned int mods_count = 0; inline unsigned int mods_count = 0;