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 "meow.h"
#include "sunshine.h"
#include "modloader.h"
#include <ruby.h>
#include <ruby/debug.h>
@ -178,6 +179,7 @@ static void mriBindingInit(){
SunshineBindingInit();
steamBindingInit();
shaderBindingInit();
ModLoader();
ModLoaderBindingInit();
keybindingsBindingInit();
lightmapBindingInit();
@ -198,6 +200,7 @@ static void mriBindingInit(){
_rb_define_module_function(mod, "puts", mkxpPuts);
_rb_define_module_function(mod, "raw_key_states", mkxpRawKeyStates);
_rb_define_module_function(mod, "mouse_in_window", mkxpMouseInWindow);
}
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 v[] = { port, utf8Proc };
return rb_marshal_load(port); // uh.. well.. it works now at least.
//return rb_funcall_with_block(marsh, rb_intern("_mkxp_load_alias"), 1, &port, utf8Proc);
return rb_marshal_load(port);
}
void fileIntBindingInit(){

View file

@ -2,6 +2,7 @@
#include <SDL3/SDL_version.h>
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_joystick.h>
#include <SDL3/SDL_hints.h>
#include <limits.h>
#include "ruby/backward/cxxanyargs.hpp"
#include "ruby/internal/arithmetic/double.h"
@ -21,6 +22,10 @@ static VALUE sunshine_set_crash_privacy(VALUE, VALUE 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){
return rb_obj_clone(self);
}
@ -57,6 +62,7 @@ void SunshineBindingInit(){
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_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"))) {
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
Fixed language selection
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
Dynamic_Light.rb
Gamepad_Colors.rb
Dynamic_Resolution.rb)
Dynamic_Resolution.rb
ModAPI.rb)
find_program(RUBY_EXE ruby
DOC "Location of the Ruby executable")

View file

@ -32,7 +32,9 @@ module Settings
# Advanced
:crashlog_privacy => false,
:streamer_privacy => false,
:SDL_HINT_INVALID_PARAM_CHECKS => false,
#controls
:gamepad_led => true,
:gamepad_deadzone => 5,
@ -44,7 +46,7 @@ module Settings
:debug_text_scene_title => true,
:debug_picture_names => false,
:debug_lightmap => false,
:SDL_HINT_SHUTDOWN_DBUS_ON_QUIT => false,
#Hidden
:is_dejavu => false,
@ -125,6 +127,12 @@ end
class Window_Settings
# if parameter is linked to data in class Settings, then default value in this structure is ignored
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" => [
{
:type => :slider,
@ -140,6 +148,7 @@ class Window_Settings
:min => 0,
:max => 100
},
{ :type => :sep, :name => "Game tracks"},
{
:type => :bool,
:name => "ITS TIME FOR FIGHT CRIME",
@ -165,13 +174,13 @@ class Window_Settings
:name => "Frameskip",
:parameter => :frameskip
},
{ :type => :sep, :name => "Other"},
{
:type => :enum,
:name => "Scaling mode",
:parameter => :scaling_mode,
:values => ["Nearest Neighbor", "Smooth(old)"]
},
{ :type => :sep, :name => "Effects"},
{
:type => :bool,
:name => "Dynamic Light",
@ -182,11 +191,6 @@ class Window_Settings
:name => "World machine shader",
:parameter => :twm_shader
},
{
:type => :bool,
:name => "Colorblind mode",
:parameter => :colorblind
},
],
"UI" => [
{
@ -208,6 +212,12 @@ class Window_Settings
},
],
"Gameplay" => [
{ :type => :sep, :name => "Vanila" },
{
:type => :bool,
:name => "Colorblind mode",
:parameter => :colorblind
},
{
:type => :enum,
:name => "Default movement",
@ -219,8 +229,9 @@ class Window_Settings
:name => "Skip Text (R)",
:parameter => :skip_text
},
{ :type => :sep, :name => "Sunshine" },
{
:type => :bool,
:type => :bool,
:name => "Purple message box for Entity",
:parameter => :en_purple_messagebox
},
@ -241,7 +252,7 @@ class Window_Settings
},
{
:type => :bool,
:name => "World machine shader on Entity footprints",
:name => "World machine shader on Entity footprints(WIP)",
:parameter => :twm_shader_footprint
},
],
@ -325,7 +336,6 @@ class Window_Settings
:parameter => :controls_nav_right,
:bind => Input::R
},
#{ :type => :sep, :name => "Other" },
{
:type => :action,
:name => "Reset Controls",
@ -333,11 +343,22 @@ class Window_Settings
},
],
"Advanced" => [
{ :type => :sep, :name => "Privacy" },
{
:type => :bool,
:name => "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" => [
@ -371,6 +392,11 @@ class Window_Settings
:name => "Debug lightmap",
:parameter => :debug_lightmap
},
{
:type => :bool,
:name => "SDL_HINT_SHUTDOWN_DBUS_ON_QUIT",
:parameter => :SDL_HINT_SHUTDOWN_DBUS_ON_QUIT
},
{ :type => :sep },
{
:type => :key,
@ -378,12 +404,20 @@ class Window_Settings
:parameter => :controls_debug,
:bind => Input::DEBUGACTION
},
{ :type => :sep },
{ :type => :seps},
{
:type => :action,
:name => "Clear image cache",
: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

View file

@ -62,13 +62,28 @@ module Settings
$game_switches[251] = value != 0
end
def skip_text(value)
# idk how to set skip_text parameter
# TODO: fix skip_text settings setter
$game_switches[253] = value != 0
end
def frameskip(value)
Graphics.frameskip = value
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

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
Main
ModAPI

View file

@ -51,10 +51,6 @@
#include "i18n.h"
#include "security.h"
#ifndef ps2
#include "modloader.h"
#endif
#include "sunshine.h"
#include "define.h"
@ -302,16 +298,6 @@ int main(int argc, char *argv[]){
return 0;
}
}
#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);
if (conf.screenMode)
@ -379,7 +365,7 @@ int main(int argc, char *argv[]){
/* Load and post key bindings */
rtData.bindingUpdateMsg.post(loadBindings(conf));
/* Start RGSS thread */
SDL_Thread *rgssThread = SDL_CreateThread(rgssThreadFun, "rgss", &rtData);

View file

@ -25,36 +25,9 @@
#include <algorithm>
#include <system_error>
#include <SDL3/SDL_system.h>
#include <physfs.h>
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){
unsigned char hash[SHA512_DIGEST_LENGTH];
@ -98,19 +71,14 @@ std::string sha256_file(const std::string &fn) {
return out;
}
#ifdef __EMSCRIPTEN__
std::string ModLoader(Config conf){ return ""; }
#else
std::string ModLoader(Config conf){
std::string path = conf.Modloader.ModsDirPath;
void ModLoader(){
std::string path = "mods";
if (!fs::exists(path) || !std::filesystem::is_directory(path)) {
Debug() << "[MODLOADER] Mods directory not found, skip.";
return "";
}
if (fs::is_empty(path)){
Debug() << "[MODLOADER] Mods directory empty, skip.";
return "";
}
//buildID - unique ID of a certain combination of mods
@ -118,16 +86,18 @@ std::string ModLoader(Config conf){
std::string buildID_tmp = "";
std::vector<std::string> mod_list = {};
try{
//1.check if any zip(mod) file, 2. calculate sha256 hash of zip(mod) files
for (const auto &entry : std::filesystem::directory_iterator(path, std::filesystem::directory_options::skip_permission_denied)) {
//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)) {
std::error_code ec;
auto p = entry.path();
if (!std::filesystem::is_regular_file(p, ec) || ec) continue;
auto ext = p.extension().string();
if (ext != ".zip") continue;
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;
mod_list.push_back(full);
mods_count++;
@ -138,84 +108,9 @@ std::string ModLoader(Config conf){
buildID = sha512(buildID_tmp);
Debug() << "[MODLOADER] BuildID: " << buildID;
std::string path3 = getCacheDir() + "/sunshine-" + buildID;
int err = 0;
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;
}
modloader_is_enabled = true;
}catch(const std::exception& e){
crash(Exception::ModLoaderError, "Something is wrong, Exception: %s ", e.what());
}
}
#endif

View file

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