Created
May 20, 2025 23:16
-
-
Save BenjaminUrquhart/7ab4dc82692c7a4c23495d95d7d57626 to your computer and use it in GitHub Desktop.
ZERO Sievert 1.1.16 mod to fix ObjectGetAllInstances and InstanceCreate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- You are about to witness war crimes. | |
| -- I stopped commenting at some point because I myself | |
| -- can't fully explain what's going on without writing a | |
| -- whole essay on the topic (which I'll probably do in the near future anyway). | |
| ObjectCreate("patcher") | |
| ObjectSetScript("patcher", "create_event", func(obj) { | |
| let check = ObjectGetAllInstances(obj_mod_generic) | |
| if ArrayLength(check) > 0 { | |
| ShowDebugMessage("Functions are already working, no need to patch") | |
| ObjectSetScript("patcher", "step_normal_event", func(_) {}) | |
| ObjectSetScript("patcher", "create_event", func(_) {}) | |
| NpcObjectDestroy(obj) | |
| } | |
| else { | |
| -- Trust the process | |
| ObjectCreate("539") | |
| ShowDebugMessage("Preparing to break sandbox") | |
| obj.gamepad = InstanceCreate(0, 0, 539) | |
| obj.gamepad.persistent = true | |
| obj.persistent = true | |
| obj.ctx = { | |
| "locals": [], | |
| "idx": 0, | |
| "lhs": func() { self.ctx.value = []; ArrayResize(self.ctx.value, 31); return -5; }, | |
| "rhs": func() { return 159 }, | |
| "op": 159, | |
| "update": 103831, | |
| "value": 103809 | |
| } | |
| obj.gamepad.array_of_input_systems = [obj.ctx] | |
| self.ctx = obj.ctx | |
| } | |
| }) | |
| ObjectSetScript("patcher", "step_normal_event", func(obj) { | |
| if ArrayLength(obj.ctx.locals) > 0 { | |
| ShowDebugMessage("Breaking sandbox") | |
| let method = obj.ctx.locals[0] | |
| let instance_exists = method(-5, 101) | |
| let instance_destroy = method(-5, 113) | |
| let script_get_name = method(-5, 780) | |
| let string_starts_with = method(-5, 299) | |
| let i = 0 | |
| while true { | |
| let name = script_get_name(i) | |
| if string_starts_with(name, "<") { | |
| break | |
| } | |
| if string_starts_with(name, "@@") { | |
| name = "INTERNAL_" + name | |
| } | |
| self[name] = method(-5, i) | |
| i += 1 | |
| } | |
| if self.instance_number(539) == 1 { | |
| ShowDebugMessage("Recreating object") | |
| self.instance_create_depth(-1, -1, -10000, 539) | |
| } | |
| instance_destroy(obj.gamepad) | |
| -- The fun part, we need to recompile all mods to pick up the changes | |
| -- while also keeping those changes intact, so we can't use the builtin | |
| -- reload functionality as that resets the function table. | |
| ShowDebugMessage("Reloading mod engine") | |
| self.variable_global_set("Mods_Failed_To_Compile", false) | |
| -- We want to avoid timing out, so we need to give ourselves near-infinite execution time. | |
| -- current_time isn't directly accessible, so we need to use a wrapper function. | |
| -- Fortunately, katspeak has one, but it's an anonymous function, so we need to use its internal name. | |
| self.get_current_time = method(-5, self.asset_get_index("anon@169929@__katspeak_get_gml_interface@__scr_katspeak_gml_interface")) | |
| self.execution_ctx = self.method_get_self(self.variable_global_get("Mods_Objects")["patcher"]["step_normal_event"]["func"]) | |
| let escapeStart = self.execution_ctx.callTime | |
| self.resetTimer = func() { | |
| let time = self.get_current_time() | |
| --ShowDebugMessage(String(self.execution_ctx.callTime) + " -> " + String(time)) | |
| self.execution_ctx.callTime = time | |
| } | |
| self.tryExecute = func(f) { | |
| self.__tryExecuteCtx = { | |
| "success": false, | |
| "CallbackSetLayoutExecute": f, | |
| "CallbackSetLayoutCheck": func() { self.__tryExecuteCtx.success = true } | |
| } | |
| -- This version of katspeak doesn't support try-catch, so we | |
| -- need a gadget in gamemaker-land that gives us something close. | |
| let executor = self.method(self.__tryExecuteCtx, self.asset_get_index("LayoutAsVerticalList@anon@56025@__uiClassCommon@__uiClassCommon")) | |
| executor() | |
| return self.__tryExecuteCtx.success | |
| } | |
| self.uiGlobal = method(-5, self.asset_get_index("__uiGlobal")) | |
| let runScript = func(script) { | |
| ShowDebugMessage("Running " + script) | |
| self.resetTimer() | |
| self.method(-5, self.asset_get_index(script))() | |
| } | |
| -- kill any mod objects that may be around | |
| instance_destroy(obj_mod_generic_solid) | |
| instance_destroy(obj_mod_generic) | |
| -- manually reload everything so we can patch the function table later | |
| runScript("mods_free_assets") | |
| runScript("gamedata_initialize_and_load") | |
| runScript("mods_clear_data") | |
| runScript("mods_audio_build_all") | |
| let katspeak = self.variable_global_get("__katspeak__") | |
| let interface = katspeak.interface | |
| -- stub functions | |
| interface.exposeFunction = func() {}; | |
| interface.exposeConstant = func() {}; | |
| interface.exposeAsset = func() {}; | |
| interface.exposeDynamicConstant = func() {}; | |
| interface.exposeFunctionByPrefix = func() {}; | |
| interface.exposeMethod = func() {}; | |
| interface.exposeAsset = func() {}; | |
| -- run this with stubs to recreate object whitelist | |
| runScript("mods_expose_functions") | |
| -- patch function table | |
| ShowDebugMessage("Patching functions") | |
| self.getObjectSandboxInfo = func(obj) { | |
| if self.is_numeric(obj) and self.object_exists(obj) { | |
| obj = self.object_get_name(obj) | |
| } | |
| let data = self.variable_struct_get(self.variable_global_get("Mods_Objects"), obj) | |
| --ShowDebugMessage(String(obj) + " -> " + String(data)) | |
| return data | |
| } | |
| interface.database["InstanceCreate"] = func(xx, yy, obj) { | |
| let data = self.getObjectSandboxInfo(obj) | |
| if data != undefined { | |
| return self.instance_create_depth(xx, yy, 0, data.object_type) | |
| } | |
| return -4 | |
| } | |
| interface.database["ObjectGetAllInstances"] = func(obj) { | |
| let data = self.getObjectSandboxInfo(obj) | |
| if data == undefined { | |
| return []; | |
| } | |
| let count = self.instance_number(data.object_type) | |
| let out = self.array_create(count) | |
| let index = 0 | |
| while index < count { | |
| out[index] = self.instance_find(data.object_type, index) | |
| index += 1 | |
| } | |
| return out | |
| } | |
| -- I'm gonna be honest, I don't know why these aren't provided normally. | |
| -- Technically it's out of scope for this mod but it makes it easier to | |
| -- tell if it's working or not. | |
| -- This only lasts as long as the sandbox escape, don't build mods around these. | |
| -- interface.database["DrawText"] = self.draw_text | |
| -- interface.database["DrawTextTransformed"] = self.draw_text_transformed | |
| -- interface.database["DrawSetFont"] = self.draw_set_font | |
| -- interface.database["DrawSetColor"] = self.draw_set_color | |
| -- interface.database["DrawSetAlpha"] = self.draw_set_alpha | |
| -- let font = 0 | |
| -- while self.font_exists(font) { | |
| -- interface.database[self.font_get_name(font)] = font | |
| -- font += 1 | |
| -- } | |
| -- finish loading | |
| runScript("mods_expose_macros") | |
| runScript("mods_kill_dangerous_functions") | |
| runScript("mods_sprites_build") | |
| runScript("mods_build_files") | |
| -- re-call init | |
| ShowDebugMessage("Re-calling init") | |
| self.variable_global_set("Mods_Meta", {}) | |
| self.mods_perform_event = method(-5, self.asset_get_index("mods_perform_event")) | |
| let startTime = self.get_current_time() | |
| self.slash = self.instance_find(self.asset_get_index("obj_mods"), 0).file_slash_format | |
| let mods = self.variable_global_get("Mods_Data") | |
| let modNames = self.variable_global_get("Mods_Data_Array") | |
| -- don't reinit ourselves | |
| let selfMod = mods["ObjectFixer"] | |
| let selfInit = selfMod["event_functions"]["init"] | |
| selfMod["event_functions"]["init"] = undefined | |
| let logError = func(mod_name, err) { | |
| let name = "" | |
| let len = self.string_length(mod_name) | |
| let index = 0 | |
| while index < len { | |
| let chr = self.string_char_at(mod_name, index + 1) | |
| let ord = self.ord(chr) | |
| if (ord >= 65 and ord <= 90) or (ord >= 97 and ord <= 122) or (ord >= 48 and ord <= 57) { | |
| name += chr | |
| } | |
| else { | |
| name += "_" | |
| } | |
| index += 1 | |
| } | |
| let filename = "CrashReport_" + name + "_" + self.string_replace_all(self.date_date_string(self.date_current_datetime()), "/", "_") + ".txt" | |
| let file = self.file_text_open_write(filename) | |
| self.file_text_write_string(file, err) | |
| self.file_text_close(file) | |
| } | |
| let trimError = func(err) { | |
| let start = "Katspeak v3.1.1:" | |
| let pos = self.string_pos(start, err) | |
| if pos > 0 { | |
| let substr = self.string_delete(err, 1, pos + self.string_length(start)) | |
| let newline = self.string_pos("\n", substr) | |
| if newline > 0 { | |
| return self.string_copy(substr, 1, newline) | |
| } | |
| return substr | |
| } | |
| return err | |
| } | |
| let failedCompile = false | |
| let initWrapper = func() { self.mods_perform_event("init") } | |
| let num = ArrayLength(modNames) | |
| let index = 0 | |
| while index < num { | |
| let name = modNames[index] | |
| let mod = mods[name] | |
| mod["mod_name"] = name | |
| let init = mod.event_functions["init"] | |
| if init != undefined { | |
| ShowDebugMessage("Running init for mod " + name) | |
| self.variable_global_set("Mods_Trace_Error", "Compile Error") | |
| self.variable_global_set("__Mods_Path_Current", mod.file_path + self.slash) | |
| self.variable_global_set("__Mods_File_Current", "init.script") | |
| self.variable_global_set("Mods_Data_Array", [name]) | |
| self.resetTimer() | |
| let success = self.tryExecute(initWrapper) | |
| self.variable_global_set("__Mods_Context_Current", selfMod) | |
| if success { | |
| mod.compile_error = {} | |
| mod.failed_to_compile = false | |
| ShowDebugMessage(name + " took " + String(self.get_current_time() - self.execution_ctx.callTime) + "ms to init") | |
| } | |
| else { | |
| failedCompile = true | |
| let trace = self.variable_global_get("Mods_Trace_Error") | |
| mod.compile_error = { "message": trimError(trace) } | |
| mod.failed_to_compile = true | |
| ShowDebugMessage("----------------------------------------------------------------------------------") | |
| ShowDebugMessage(name + " failed to init") | |
| ShowDebugMessage(trace) | |
| ShowDebugMessage("----------------------------------------------------------------------------------") | |
| logError(name, trace) | |
| } | |
| } | |
| else if mod.failed_to_compile { | |
| failedCompile = true | |
| let err = mod.compile_error | |
| ShowDebugMessage("Mod " + name + " failed to compile:\n" + String(err)) | |
| if self.is_struct(err) { | |
| err = err["message"] | |
| } | |
| else { | |
| err = String(err) | |
| } | |
| logError(name, err) | |
| } | |
| else { | |
| mod.compile_error = {} | |
| } | |
| index += 1 | |
| } | |
| self.resetTimer() | |
| self.variable_global_set("Mods_Failed_To_Compile", failedCompile) | |
| self.variable_global_set("__Mods_Context_Current", selfMod) | |
| self.variable_global_set("Mods_Data_Array", modNames) | |
| selfMod.compile_error = {} | |
| selfMod.failed_to_compile = false | |
| selfMod["event_functions"]["init"] = selfInit | |
| ShowDebugMessage("Init took " + String(self.get_current_time() - startTime) + "ms total") | |
| if failedCompile and instance_exists(self.asset_get_index("obj_main_menu")) { | |
| let rootLayer = self.uiGlobal().__defaultOnion.__layerArray[0] | |
| let ui = undefined | |
| if rootLayer.Find != undefined { | |
| ui = rootLayer.Find("main frame") | |
| } | |
| else { | |
| let children = rootLayer.__struct.__children | |
| let len = ArrayLength(children) | |
| let index = 0 | |
| while index < len { | |
| let child = children[index] | |
| ShowDebugMessage(child) | |
| if child.__name == "main frame" { | |
| ui = child | |
| break | |
| } | |
| index += 1 | |
| } | |
| } | |
| if ui != undefined { | |
| ShowDebugMessage("Reloading mod UI") | |
| ui.ReplaceFromFile("ZS_vanilla/ui/mm_mods.ui") | |
| } | |
| } | |
| ShowDebugMessage("Done patching in " + String(self.get_current_time() - escapeStart) + "ms, InstanceCreate and ObjectGetAllInstances should function as intended now") | |
| } | |
| }) | |
| ObjectSpawn("patcher", 0, 0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment