Skip to content

Instantly share code, notes, and snippets.

@BenjaminUrquhart
Created May 20, 2025 23:16
Show Gist options
  • Select an option

  • Save BenjaminUrquhart/7ab4dc82692c7a4c23495d95d7d57626 to your computer and use it in GitHub Desktop.

Select an option

Save BenjaminUrquhart/7ab4dc82692c7a4c23495d95d7d57626 to your computer and use it in GitHub Desktop.
ZERO Sievert 1.1.16 mod to fix ObjectGetAllInstances and InstanceCreate
-- 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