From 0c65be3833a15225cfa9c2e8d911bb41cbb9828c Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Fri, 2 Feb 2024 19:34:08 -0300 Subject: [PATCH 01/16] WIP rollback function hook --- src/game_api/script/events.cpp | 10 ++++ src/game_api/script/events.hpp | 1 + src/game_api/script/lua_backend.cpp | 21 +++++++++ src/game_api/script/lua_backend.hpp | 3 ++ src/game_api/script/lua_vm.cpp | 6 ++- src/game_api/search.cpp | 9 ++++ src/game_api/state.cpp | 71 +++++++++++++++++++++++++++++ src/game_api/state.hpp | 3 +- 8 files changed, 122 insertions(+), 2 deletions(-) diff --git a/src/game_api/script/events.cpp b/src/game_api/script/events.cpp index 631e8eb85..acc3f1004 100644 --- a/src/game_api/script/events.cpp +++ b/src/game_api/script/events.cpp @@ -471,3 +471,13 @@ void post_event(ON event) return true; }); } + +void heap_clone_event(ON event, StateMemory* from, StateMemory* to) +{ + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + backend->on_clone_heap(event, from, to); + return true; + }); +} diff --git a/src/game_api/script/events.hpp b/src/game_api/script/events.hpp index 795a5ae25..d1a6ef249 100644 --- a/src/game_api/script/events.hpp +++ b/src/game_api/script/events.hpp @@ -30,6 +30,7 @@ void post_init_layer(LAYER layer); void post_unload_layer(LAYER layer); void post_room_generation(); void post_level_generation(); +void heap_clone_event(ON event, StateMemory* from, StateMemory* to); void on_death_message(STRINGID stringid); std::optional pre_get_feat(FEAT feat); diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index fd64ee738..78060f9e0 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -1814,3 +1814,24 @@ void LuaBackend::on_post(ON event) } } } + +void LuaBackend::on_clone_heap(ON event, StateMemory* from, StateMemory* to) +{ + if (!get_enabled()) + return; + + auto now = get_frame_count(); + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == event) + { + set_current_callback(-1, id, CallbackType::Normal); + handle_function(this, callback.func, from, to); + clear_current_callback(); + callback.lastRan = now; + } + } +} diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index f3d5145e3..3d418c109 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -135,6 +135,8 @@ enum class ON BLOCKED_UPDATE, BLOCKED_GAME_LOOP, BLOCKED_PROCESS_INPUT, + PRE_CLONE_HEAP, + POST_CLONE_HEAP, }; struct IntOption @@ -440,6 +442,7 @@ class LuaBackend void load_user_data(); bool on_pre(ON event); void on_post(ON event); + void on_clone_heap(ON event, StateMemory* from, StateMemory* to); }; template diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index c79d999c2..73c5f0056 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2435,7 +2435,11 @@ end "BLOCKED_GAME_LOOP", ON::BLOCKED_GAME_LOOP, "BLOCKED_PROCESS_INPUT", - ON::BLOCKED_PROCESS_INPUT); + ON::BLOCKED_PROCESS_INPUT, + "PRE_CLONE_HEAP", + ON::PRE_CLONE_HEAP, + "POST_CLONE_HEAP", + ON::POST_CLONE_HEAP); /* ON // LOGO diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 89338b408..4a4302084 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -1826,6 +1826,15 @@ std::unordered_map g_address_rules{ .at_exe() .function_start(), }, + { + // Put write bp on state.win_state and enter a multiplayer game + "heap_clone"sv, + PatternCommandBuffer{} + .find_inst("4c 8d 05 f4 ca 27 00"_gh) + .find_next_inst("eb 27"_gh) + .offset(-0xC) + .at_exe() + }, { // ^ writes to state.pause on state.loading == 3 "unpause_level"sv, diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index a3ad961d0..9ad69f16c 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -302,6 +302,7 @@ State& State::get() init_state_update_hook(); init_process_input_hook(); init_game_loop_hook(); + init_state_clone_hook(); auto bucket = Bucket::get(); if (!bucket->patches_applied) @@ -694,6 +695,76 @@ void init_state_update_hook() } } +ExecutableMemory g_heap_clone_redirect; +using OnHeapClone = void(uint64_t heap_to, uint64_t heap_container_from); +OnHeapClone* g_heap_clone_trampoline{nullptr}; +void HeapClone(uint64_t heap_to, uint64_t heap_container_from) +{ + uint64_t location = memory_read(State::get().location); + StateMemory* state_from = reinterpret_cast(memory_read(heap_container_from + 0x88) + location); + StateMemory* state_to = reinterpret_cast(heap_to + location); + heap_clone_event(ON::PRE_CLONE_HEAP, state_from, state_to); + // g_heap_clone_trampoline(heap_container_to, heap_container_from, heap_to); + // heap_clone_event(ON::POST_CLONE_HEAP, state_from,state_to); +} + +// Original function params: clone_heap(ThreadStorageContainer to, ThreadStorageContainer from) +// HeapContainer has heap1 and heap2 variables, and some sort of timer, that just increases constantly, I guess to handle the rollback and multi-threaded stuff +// The rest of what HeapContainer has is unknown for now +// After writing to a chosen storage from the content of `from->heap1`, sets `to->heap2` to the newly copied thread storage +void init_state_clone_hook() +{ + auto heap_clone = get_address("heap_clone"); + // g_heap_clone_trampoline = (OnHeapClone*)(heap_clone+0x65); + // Hook the function after it has chosen a thread storage to write to, and pass it to the hook + size_t heap_clone_redirect_from_addr = heap_clone+0x65; + DEBUG("HEAP_CLONE: {}\n", static_cast(heap_clone_redirect_from_addr)); + const std::string redirect_code = fmt::format( + "\x51" // PUSH RCX + "\x52" // PUSH RDX + "\x41\x50" // PUSH R8 + "\x41\x51" // PUSH R9 + "\x48\x83\xEC\x28" // SUB RSP, 28 // Shadow space + Stack alignment + "\x4C\x89\xC9" // MOV RCX, R9 == heap_to + "\x48\xb8{}" // MOV RAX, &HeapClone + "\xff\xd0" // CALL RAX + "\x48\x83\xC4\x28" // ADD RSP, 28 + "\x41\x59" // POP R9 + "\x41\x58" // POP R8 + "\x5A" // POP RDX + "\x59" // POP RCX + // Original Code Begin + "\x48\x8b\x82\x88\x00\x00\x00" // MOV RAX, qword ptr [RDX + 0x88] + "\x4d\x89\xca" // MOV R10, R9 + "\x49\x29\xc2" // SUB R10, RAX + // Original Code End + "\x48\xbe{}" // MOV RSI, jump_back_addr + "\xff\xe6"sv, // JMP RSI + to_le_bytes(&HeapClone), + to_le_bytes(heap_clone+0x72)); + + g_heap_clone_redirect = ExecutableMemory{redirect_code}; + + std::string code = fmt::format( + "\x48\xb8{}" // MOV RAX, g_heap_clone_redirect.get() + "\xff\xe0" // JMP RAX + "\x90"sv, // NOP + to_le_bytes((size_t)g_heap_clone_redirect.get())); + + write_mem_prot(heap_clone_redirect_from_addr, code, true); + // DetourTransactionBegin(); + // DetourUpdateThread(GetCurrentThread()); + // DetourAttach((void**)&g_heap_clone_trampoline, &HeapClone); + + // const LONG error = DetourTransactionCommit(); + // if (error != NO_ERROR) + // { + // DEBUG("Failed hooking heap_clone stuff: {}\n", error); + // } else { + // write_mem_prot(heap_clone+0x149, "\xFF\x74\x24\x20", true); + // } +} + using OnProcessInput = void(void*); OnProcessInput* g_process_input_trampoline{nullptr}; void ProcessInput(void* s) diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index 8345bec20..2de0f22f5 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -387,17 +387,18 @@ struct State SaveData* savedata(); LiquidPhysicsEngine* get_correct_liquid_engine(ENT_TYPE liquid_type); + size_t location; private: State(size_t addr) : location(addr){}; - size_t location; State(const State&) = delete; State& operator=(const State&) = delete; }; void init_state_update_hook(); void init_process_input_hook(); void init_game_loop_hook(); +void init_state_clone_hook(); uint8_t enum_to_layer(const LAYER layer, std::pair& player_position); uint8_t enum_to_layer(const LAYER layer); From 5a590a5c527ad72d7a54ab452339b2ae4626826c Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Fri, 9 Feb 2024 17:51:20 -0300 Subject: [PATCH 02/16] add local_data to StateMemory, to allow script data that gets affected by rollback --- src/game_api/script/events.cpp | 4 ++-- src/game_api/script/events.hpp | 2 +- src/game_api/script/lua_backend.cpp | 11 +++++++++-- src/game_api/script/lua_backend.hpp | 3 ++- src/game_api/script/lua_vm.cpp | 13 ++++++++++--- src/game_api/script/usertypes/state_lua.cpp | 19 +++++++++++++++++++ src/game_api/state.cpp | 2 +- 7 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/game_api/script/events.cpp b/src/game_api/script/events.cpp index acc3f1004..404337aa3 100644 --- a/src/game_api/script/events.cpp +++ b/src/game_api/script/events.cpp @@ -472,12 +472,12 @@ void post_event(ON event) }); } -void heap_clone_event(ON event, StateMemory* from, StateMemory* to) +void pre_heap_clone_event(StateMemory* from, StateMemory* to) { LuaBackend::for_each_backend( [&](LuaBackend::LockedBackend backend) { - backend->on_clone_heap(event, from, to); + backend->pre_clone_heap(from, to); return true; }); } diff --git a/src/game_api/script/events.hpp b/src/game_api/script/events.hpp index d1a6ef249..fec356457 100644 --- a/src/game_api/script/events.hpp +++ b/src/game_api/script/events.hpp @@ -30,7 +30,7 @@ void post_init_layer(LAYER layer); void post_unload_layer(LAYER layer); void post_room_generation(); void post_level_generation(); -void heap_clone_event(ON event, StateMemory* from, StateMemory* to); +void pre_heap_clone_event(StateMemory* from, StateMemory* to); void on_death_message(STRINGID stringid); std::optional pre_get_feat(FEAT feat); diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 78060f9e0..b08d23c1f 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -1815,10 +1815,17 @@ void LuaBackend::on_post(ON event) } } -void LuaBackend::on_clone_heap(ON event, StateMemory* from, StateMemory* to) +void LuaBackend::pre_clone_heap(StateMemory* from, StateMemory* to) { if (!get_enabled()) return; + if (local_datas.contains(from)) { + sol::object from_data = local_datas[from]; + if (from_data != sol::lua_nil) + { + local_datas[to] = (*vm)["deepcopy_object"](from_data); + } + } auto now = get_frame_count(); for (auto& [id, callback] : callbacks) @@ -1826,7 +1833,7 @@ void LuaBackend::on_clone_heap(ON event, StateMemory* from, StateMemory* to) if (is_callback_cleared(id)) continue; - if (callback.screen == event) + if (callback.screen == ON::PRE_CLONE_HEAP) { set_current_callback(-1, id, CallbackType::Normal); handle_function(this, callback.func, from, to); diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index 3d418c109..2e088cedc 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -319,6 +319,7 @@ class LuaBackend std::unordered_map script_input; std::unordered_set windows; std::unordered_set console_commands; + std::unordered_map local_datas; bool manual_save{false}; uint32_t last_save{0}; @@ -442,7 +443,7 @@ class LuaBackend void load_user_data(); bool on_pre(ON event); void on_post(ON event); - void on_clone_heap(ON event, StateMemory* from, StateMemory* to); + void pre_clone_heap(StateMemory* from, StateMemory* to); }; template diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 73c5f0056..39d5bf38d 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2249,6 +2249,15 @@ end /// Initializes some seedeed run related values and loads the character select screen, as if starting a new seeded run after entering the seed. lua["play_seeded"] = init_seeded; + lua.script(R"##( + function deepcopy_object(obj) + if type(obj) ~= 'table' then return obj end + local res = {} + for k, v in pairs(obj) do res[deepcopy_object(k)] = deepcopy_object(v) end + res = setmetatable(res, getmetatable(obj)) + return res + end + )##"); lua.create_named_table("INPUTS", "NONE", 0x0, "JUMP", 0x1, "WHIP", 0x2, "BOMB", 0x4, "ROPE", 0x8, "RUN", 0x10, "DOOR", 0x20, "MENU", 0x40, "JOURNAL", 0x80, "LEFT", 0x100, "RIGHT", 0x200, "UP", 0x400, "DOWN", 0x800); lua.create_named_table("MENU_INPUT", "NONE", 0x0, "SELECT", 0x1, "BACK", 0x2, "DELETE", 0x4, "RANDOM", 0x8, "JOURNAL", 0x10, "LEFT", 0x20, "RIGHT", 0x40, "UP", 0x80, "DOWN", 0x100); @@ -2437,9 +2446,7 @@ end "BLOCKED_PROCESS_INPUT", ON::BLOCKED_PROCESS_INPUT, "PRE_CLONE_HEAP", - ON::PRE_CLONE_HEAP, - "POST_CLONE_HEAP", - ON::POST_CLONE_HEAP); + ON::PRE_CLONE_HEAP); /* ON // LOGO diff --git a/src/game_api/script/usertypes/state_lua.cpp b/src/game_api/script/usertypes/state_lua.cpp index e6fb19d85..bc6a84bbe 100644 --- a/src/game_api/script/usertypes/state_lua.cpp +++ b/src/game_api/script/usertypes/state_lua.cpp @@ -22,6 +22,7 @@ #include "screen_arena.hpp" // IWYU pragma: keep #include "state.hpp" // for StateMemory, State, StateMemory::a... #include "state_structs.hpp" // for ArenaConfigArenas, ArenaConfigItems +#include "script/lua_backend.hpp" // for LuaBackend namespace NState { @@ -410,6 +411,24 @@ void register_usertypes(sol::state& lua) statememory_type["liquid"] = &StateMemory::liquid_physics; statememory_type["next_entity_uid"] = &StateMemory::next_entity_uid; statememory_type["room_owners"] = &StateMemory::room_owners; + + auto get_local_data = [](StateMemory& state) -> sol::object + { + auto backend = LuaBackend::get_calling_backend(); + auto local_datas = backend->local_datas; + if (local_datas.contains(&state)) { + return local_datas[&state]; + } + return sol::nil; + }; + + auto set_local_data = [](StateMemory& state, sol::object user_data) -> void + { + auto backend = LuaBackend::get_calling_backend(); + backend->local_datas[&state] = user_data; + }; + + statememory_type["local_data"] = sol::property(get_local_data, set_local_data); lua.create_named_table("FADE", "NONE", 0, "OUT", 1, "LOAD", 2, "IN", 3); diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index 9ad69f16c..93b5a37fe 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -703,9 +703,9 @@ void HeapClone(uint64_t heap_to, uint64_t heap_container_from) uint64_t location = memory_read(State::get().location); StateMemory* state_from = reinterpret_cast(memory_read(heap_container_from + 0x88) + location); StateMemory* state_to = reinterpret_cast(heap_to + location); - heap_clone_event(ON::PRE_CLONE_HEAP, state_from, state_to); // g_heap_clone_trampoline(heap_container_to, heap_container_from, heap_to); // heap_clone_event(ON::POST_CLONE_HEAP, state_from,state_to); + pre_heap_clone_event(state_from, state_to); } // Original function params: clone_heap(ThreadStorageContainer to, ThreadStorageContainer from) From 2543422631a40dc7d71883819b5e6a8f62683593 Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Sun, 11 Feb 2024 01:28:23 -0300 Subject: [PATCH 03/16] hook rollback function with patch_and_redirect and fix it --- src/game_api/memory.cpp | 4 ++-- src/game_api/state.cpp | 42 ++++------------------------------------- 2 files changed, 6 insertions(+), 40 deletions(-) diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index e17e88100..431a38054 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -192,10 +192,10 @@ size_t patch_and_redirect(size_t addr, size_t replace_size, const std::string_vi } else { - std::memcpy(new_code + data_size_to_move, payload.data(), payload.size()); + std::memcpy(new_code, payload.data(), payload.size()); if (!just_nop) - std::memcpy(new_code, (void*)addr, data_size_to_move); + std::memcpy(new_code + payload.size(), (void*)addr, data_size_to_move); } size_t return_addr = addr + replace_size; diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index 93b5a37fe..42c118350 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -695,16 +695,11 @@ void init_state_update_hook() } } -ExecutableMemory g_heap_clone_redirect; -using OnHeapClone = void(uint64_t heap_to, uint64_t heap_container_from); -OnHeapClone* g_heap_clone_trampoline{nullptr}; void HeapClone(uint64_t heap_to, uint64_t heap_container_from) { uint64_t location = memory_read(State::get().location); StateMemory* state_from = reinterpret_cast(memory_read(heap_container_from + 0x88) + location); StateMemory* state_to = reinterpret_cast(heap_to + location); - // g_heap_clone_trampoline(heap_container_to, heap_container_from, heap_to); - // heap_clone_event(ON::POST_CLONE_HEAP, state_from,state_to); pre_heap_clone_event(state_from, state_to); } @@ -715,10 +710,8 @@ void HeapClone(uint64_t heap_to, uint64_t heap_container_from) void init_state_clone_hook() { auto heap_clone = get_address("heap_clone"); - // g_heap_clone_trampoline = (OnHeapClone*)(heap_clone+0x65); // Hook the function after it has chosen a thread storage to write to, and pass it to the hook size_t heap_clone_redirect_from_addr = heap_clone+0x65; - DEBUG("HEAP_CLONE: {}\n", static_cast(heap_clone_redirect_from_addr)); const std::string redirect_code = fmt::format( "\x51" // PUSH RCX "\x52" // PUSH RDX @@ -732,37 +725,10 @@ void init_state_clone_hook() "\x41\x59" // POP R9 "\x41\x58" // POP R8 "\x5A" // POP RDX - "\x59" // POP RCX - // Original Code Begin - "\x48\x8b\x82\x88\x00\x00\x00" // MOV RAX, qword ptr [RDX + 0x88] - "\x4d\x89\xca" // MOV R10, R9 - "\x49\x29\xc2" // SUB R10, RAX - // Original Code End - "\x48\xbe{}" // MOV RSI, jump_back_addr - "\xff\xe6"sv, // JMP RSI - to_le_bytes(&HeapClone), - to_le_bytes(heap_clone+0x72)); - - g_heap_clone_redirect = ExecutableMemory{redirect_code}; - - std::string code = fmt::format( - "\x48\xb8{}" // MOV RAX, g_heap_clone_redirect.get() - "\xff\xe0" // JMP RAX - "\x90"sv, // NOP - to_le_bytes((size_t)g_heap_clone_redirect.get())); - - write_mem_prot(heap_clone_redirect_from_addr, code, true); - // DetourTransactionBegin(); - // DetourUpdateThread(GetCurrentThread()); - // DetourAttach((void**)&g_heap_clone_trampoline, &HeapClone); - - // const LONG error = DetourTransactionCommit(); - // if (error != NO_ERROR) - // { - // DEBUG("Failed hooking heap_clone stuff: {}\n", error); - // } else { - // write_mem_prot(heap_clone+0x149, "\xFF\x74\x24\x20", true); - // } + "\x59"sv, // POP RCX + to_le_bytes(&HeapClone)); + + patch_and_redirect(heap_clone_redirect_from_addr, 7, redirect_code, false, 0, false); } using OnProcessInput = void(void*); From a91419ce465b4c591dc0ca15af326ab48fffa15b Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Wed, 6 Mar 2024 01:00:27 -0300 Subject: [PATCH 04/16] Move local_state_datas to LocalStateData struct --- src/game_api/script/lua_backend.cpp | 11 ++++++++--- src/game_api/script/lua_backend.hpp | 9 ++++++++- src/game_api/script/usertypes/state_lua.cpp | 6 +++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 7d540d51a..a66fc2f65 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -78,6 +78,11 @@ LuaBackend::~LuaBackend() } } +LocalStateData& LuaBackend::get_locals() +{ + return local_state_datas[State::get().ptr()]; +} + void LuaBackend::clear() { clear_all_callbacks(); @@ -1819,11 +1824,11 @@ void LuaBackend::pre_clone_heap(StateMemory* from, StateMemory* to) { if (!get_enabled()) return; - if (local_datas.contains(from)) { - sol::object from_data = local_datas[from]; + if (local_state_datas.contains(from)) { + sol::object from_data = local_state_datas[from].user_data; if (from_data != sol::lua_nil) { - local_datas[to] = (*vm)["deepcopy_object"](from_data); + local_state_datas[to].user_data = (*vm)["deepcopy_object"](from_data); } } diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index a7feeec92..d0430061c 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -277,6 +277,11 @@ class SoundManager; class LuaConsole; struct RenderInfo; +struct LocalStateData +{ + sol::object user_data; +}; + class LuaBackend : public HookHandler, public HookHandler, @@ -323,7 +328,7 @@ class LuaBackend std::unordered_map script_input; std::unordered_set windows; std::unordered_set console_commands; - std::unordered_map local_datas; + std::unordered_map local_state_datas; bool manual_save{false}; uint32_t last_save{0}; @@ -342,6 +347,8 @@ class LuaBackend LuaBackend(SoundManager* sound_manager, LuaConsole* console); virtual ~LuaBackend(); + LocalStateData& get_locals(); + void copy_locals(StateMemory* from, StateMemory* to); void clear(); void clear_all_callbacks(); bool update(); diff --git a/src/game_api/script/usertypes/state_lua.cpp b/src/game_api/script/usertypes/state_lua.cpp index f24c54537..bc9b73ab1 100644 --- a/src/game_api/script/usertypes/state_lua.cpp +++ b/src/game_api/script/usertypes/state_lua.cpp @@ -417,9 +417,9 @@ void register_usertypes(sol::state& lua) auto get_local_data = [](StateMemory& state) -> sol::object { auto backend = LuaBackend::get_calling_backend(); - auto local_datas = backend->local_datas; + auto local_datas = backend->local_state_datas; if (local_datas.contains(&state)) { - return local_datas[&state]; + return local_datas[&state].user_data; } return sol::nil; }; @@ -427,7 +427,7 @@ void register_usertypes(sol::state& lua) auto set_local_data = [](StateMemory& state, sol::object user_data) -> void { auto backend = LuaBackend::get_calling_backend(); - backend->local_datas[&state] = user_data; + backend->local_state_datas[&state].user_data = user_data; }; statememory_type["local_data"] = sol::property(get_local_data, set_local_data); From e70fa55960c8961b2a0df659376472fbff496aef Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Thu, 7 Mar 2024 22:53:04 -0300 Subject: [PATCH 05/16] make more callbacks online compatible, fix deadlock script state is now in local_state_datas The deadlock was between a thread with g_all_backends_mutex and another with global_lua_lock --- src/game_api/script/lua_backend.cpp | 107 ++++++++++++++++------------ src/game_api/script/lua_backend.hpp | 2 +- src/game_api/state.cpp | 4 ++ src/game_api/state.hpp | 1 + 4 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 94b5dc77b..217f906b6 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -36,7 +36,6 @@ #include "usertypes/vanilla_render_lua.hpp" // for VanillaRenderContext #include "window_api.hpp" // for get_window -std::recursive_mutex g_all_backends_mutex; std::vector> g_all_backends; std::unordered_map g_hotkeys; int g_hotkey_count = 0; @@ -44,11 +43,16 @@ int g_hotkey_count = 0; LuaBackend::LuaBackend(SoundManager* sound_mgr, LuaConsole* con) : lua{get_lua_vm(sound_mgr), sol::create}, vm{acquire_lua_vm(sound_mgr)}, sound_manager{sound_mgr}, console{con} { - g_state = State::get().ptr_main(); + g_state = State::get().ptr_local(); + if (g_state == nullptr) + { + g_state = State::get().ptr_main(); + } + ScriptState& state = local_state_datas[g_state].state; state.screen = g_state->screen; state.time_level = g_state->time_level; state.time_total = g_state->time_total; - state.time_global = get_frame_count_main(); + state.time_global = State::get_frame_count(g_state); state.frame = state.frame; state.loading = g_state->loading; state.reset = (g_state->quest_flags & 1); @@ -56,7 +60,7 @@ LuaBackend::LuaBackend(SoundManager* sound_mgr, LuaConsole* con) populate_lua_env(lua); - std::lock_guard lock{g_all_backends_mutex}; + std::lock_guard lock{global_lua_lock}; g_all_backends.emplace_back(new ProtectedBackend{this}); self = g_all_backends.back().get(); } @@ -76,7 +80,7 @@ LuaBackend::~LuaBackend() } { - std::lock_guard lock{g_all_backends_mutex}; + std::lock_guard lock{global_lua_lock}; std::erase_if(g_all_backends, [=](const std::unique_ptr& protected_backend) { return protected_backend.get() == self; }); } @@ -279,29 +283,31 @@ bool LuaBackend::update() } /*moved to pre_load_screen - if (g_state->loading == 1 && g_state->loading != state.loading && g_state->screen_next != (int)ON::OPTIONS && g_state->screen != (int)ON::OPTIONS && g_state->screen_last != (int)ON::OPTIONS) + if (state->loading == 1 && state->loading != script_state.loading && state->screen_next != (int)ON::OPTIONS && state->screen != (int)ON::OPTIONS && state->screen_last != (int)ON::OPTIONS) { level_timers.clear(); script_input.clear(); clear_custom_shopitem_names(); }*/ - if (g_state->screen != state.screen) + ScriptState& script_state = get_locals().state; + StateMemory* state = State::get().ptr(); + if (state->screen != script_state.screen) { if (on_screen) on_screen.value()(); } - if (on_frame && g_state->time_level != state.time_level && g_state->screen == (int)ON::LEVEL) + if (on_frame && state->time_level != script_state.time_level && state->screen == (int)ON::LEVEL) { on_frame.value()(); } - if (g_state->screen == (int)ON::CAMP && g_state->screen_last != (int)ON::OPTIONS && g_state->loading != state.loading && g_state->loading == 3 && g_state->time_level == 1) + if (state->screen == (int)ON::CAMP && state->screen_last != (int)ON::OPTIONS && state->loading != script_state.loading && state->loading == 3 && state->time_level == 1) { if (on_camp) on_camp.value()(); } - if (g_state->screen == (int)ON::LEVEL && g_state->screen_last != (int)ON::OPTIONS && g_state->loading != state.loading && g_state->loading == 3 && g_state->time_level == 1) + if (state->screen == (int)ON::LEVEL && state->screen_last != (int)ON::OPTIONS && state->loading != script_state.loading && state->loading == 3 && state->time_level == 1) { - if (g_state->level_count == 0) + if (state->level_count == 0) { if (on_start) on_start.value()(); @@ -309,17 +315,17 @@ bool LuaBackend::update() if (on_level) on_level.value()(); } - if (g_state->screen == (int)ON::TRANSITION && state.screen != (int)ON::TRANSITION) + if (state->screen == (int)ON::TRANSITION && script_state.screen != (int)ON::TRANSITION) { if (on_transition) on_transition.value()(); } - if (g_state->screen == (int)ON::DEATH && state.screen != (int)ON::DEATH) + if (state->screen == (int)ON::DEATH && script_state.screen != (int)ON::DEATH) { if (on_death) on_death.value()(); } - if ((g_state->screen == (int)ON::WIN && state.screen != (int)ON::WIN) || (g_state->screen == (int)ON::CONSTELLATION && state.screen != (int)ON::CONSTELLATION)) + if ((state->screen == (int)ON::WIN && script_state.screen != (int)ON::WIN) || (state->screen == (int)ON::CONSTELLATION && script_state.screen != (int)ON::CONSTELLATION)) { if (on_win) on_win.value()(); @@ -377,7 +383,7 @@ bool LuaBackend::update() for (auto it = global_timers.begin(); it != global_timers.end();) { - int now = get_frame_count(); + int now = State::get_frame_count(state); if (auto cb = std::get_if(&it->second)) { if (now >= cb->lastRan + cb->interval && !is_callback_cleared(it->first)) @@ -414,7 +420,7 @@ bool LuaBackend::update() } } - auto now = get_frame_count(); + auto now = State::get_frame_count(state); for (auto& [id, callback] : load_callbacks) { if (callback.lastRan < 0) @@ -432,17 +438,17 @@ bool LuaBackend::update() continue; set_current_callback(-1, id, CallbackType::Normal); - if ((ON)g_state->screen == callback.screen && g_state->screen != state.screen && g_state->screen_last != (int)ON::OPTIONS) // game screens + if ((ON)state->screen == callback.screen && state->screen != script_state.screen && state->screen_last != (int)ON::OPTIONS) // game screens { handle_function(this, callback.func); callback.lastRan = now; } - else if (callback.screen == ON::LEVEL && g_state->screen == (int)ON::LEVEL && g_state->screen_last != (int)ON::OPTIONS && state.loading != g_state->loading && g_state->loading == 3 && g_state->time_level <= 1) + else if (callback.screen == ON::LEVEL && state->screen == (int)ON::LEVEL && state->screen_last != (int)ON::OPTIONS && script_state.loading != state->loading && state->loading == 3 && state->time_level <= 1) { handle_function(this, callback.func); callback.lastRan = now; } - else if (callback.screen == ON::CAMP && g_state->screen == (int)ON::CAMP && g_state->screen_last != (int)ON::OPTIONS && state.loading != g_state->loading && g_state->loading == 3 && g_state->time_level == 1) + else if (callback.screen == ON::CAMP && state->screen == (int)ON::CAMP && state->screen_last != (int)ON::OPTIONS && script_state.loading != state->loading && state->loading == 3 && state->time_level == 1) { handle_function(this, callback.func); callback.lastRan = now; @@ -453,7 +459,7 @@ bool LuaBackend::update() { case ON::FRAME: { - if (g_state->time_level != state.time_level && g_state->screen == (int)ON::LEVEL) + if (state->time_level != script_state.time_level && state->screen == (int)ON::LEVEL) { handle_function(this, callback.func); callback.lastRan = now; @@ -462,8 +468,8 @@ bool LuaBackend::update() } case ON::GAMEFRAME: { - if (!g_state->pause && get_frame_count() != state.time_global && - ((g_state->screen >= (int)ON::CAMP && g_state->screen <= (int)ON::DEATH) || g_state->screen == (int)ON::ARENA_MATCH)) + if (!state->pause && State::get_frame_count(state) != script_state.time_global && + ((state->screen >= (int)ON::CAMP && state->screen <= (int)ON::DEATH) || state->screen == (int)ON::ARENA_MATCH)) { handle_function(this, callback.func); callback.lastRan = now; @@ -472,7 +478,7 @@ bool LuaBackend::update() } case ON::SCREEN: { - if (g_state->screen != state.screen) + if (state->screen != script_state.screen) { handle_function(this, callback.func); callback.lastRan = now; @@ -481,7 +487,7 @@ bool LuaBackend::update() } case ON::START: { - if (g_state->screen == (int)ON::LEVEL && g_state->screen_last != (int)ON::OPTIONS && g_state->level_count == 0 && g_state->loading != state.loading && g_state->loading == 3 && g_state->time_level <= 1) + if (state->screen == (int)ON::LEVEL && state->screen_last != (int)ON::OPTIONS && state->level_count == 0 && state->loading != script_state.loading && state->loading == 3 && state->time_level <= 1) { handle_function(this, callback.func); callback.lastRan = now; @@ -490,7 +496,7 @@ bool LuaBackend::update() } case ON::LOADING: { - if (g_state->loading > 0 && g_state->loading != state.loading) + if (state->loading > 0 && state->loading != script_state.loading) { handle_function(this, callback.func); callback.lastRan = now; @@ -499,7 +505,7 @@ bool LuaBackend::update() } case ON::RESET: { - if ((g_state->quest_flags & 1) > 0 && (g_state->quest_flags & 1) != state.reset) + if ((state->quest_flags & 1) > 0 && (state->quest_flags & 1) != script_state.reset) { handle_function(this, callback.func); callback.lastRan = now; @@ -512,7 +518,7 @@ bool LuaBackend::update() } clear_current_callback(); } - const int now_l = g_state->time_level; + const int now_l = state->time_level; for (auto it = level_timers.begin(); it != level_timers.end();) { if (auto cb = std::get_if(&it->second)) @@ -556,7 +562,7 @@ bool LuaBackend::update() for (auto& [id, callback] : save_callbacks) { set_current_callback(-1, id, CallbackType::Normal); - if ((g_state->loading != state.loading && g_state->loading == 1) || manual_save) + if ((state->loading != script_state.loading && state->loading == 1) || manual_save) { handle_function(this, callback.func, SaveContext{get_root(), get_name()}); callback.lastRan = now; @@ -564,14 +570,14 @@ bool LuaBackend::update() clear_current_callback(); } - state.screen = g_state->screen; - state.time_level = g_state->time_level; - state.time_total = g_state->time_total; - state.time_global = get_frame_count(); - state.frame = get_frame_count(); - state.loading = g_state->loading; - state.reset = (g_state->quest_flags & 1); - state.quest_flags = g_state->quest_flags; + script_state.screen = state->screen; + script_state.time_level = state->time_level; + script_state.time_total = state->time_total; + script_state.time_global = get_frame_count(); + script_state.frame = get_frame_count(); + script_state.loading = state->loading; + script_state.reset = (state->quest_flags & 1); + script_state.quest_flags = state->quest_flags; if (manual_save) { @@ -1705,7 +1711,7 @@ void LuaBackend::set_error(std::string err) */ void LuaBackend::for_each_backend(std::function fun, bool stop_propagation) { - std::lock_guard lock{g_all_backends_mutex}; + std::lock_guard lock{global_lua_lock}; for (std::unique_ptr& backend : g_all_backends) { if (!fun(backend->Lock()) && stop_propagation) @@ -1720,7 +1726,7 @@ LuaBackend::LockedBackend LuaBackend::get_backend(std::string_view id) } std::optional LuaBackend::get_backend_safe(std::string_view id) { - std::lock_guard lock{g_all_backends_mutex}; + std::lock_guard lock{global_lua_lock}; for (std::unique_ptr& backend : g_all_backends) { LockedBackend locked = backend->Lock(); @@ -1737,7 +1743,7 @@ LuaBackend::LockedBackend LuaBackend::get_backend_by_id(std::string_view id, std } std::optional LuaBackend::get_backend_by_id_safe(std::string_view id, std::string_view ver) { - std::lock_guard lock{g_all_backends_mutex}; + std::lock_guard lock{global_lua_lock}; for (std::unique_ptr& backend : g_all_backends) { LockedBackend locked = backend->Lock(); @@ -1871,18 +1877,27 @@ void LuaBackend::on_post(ON event) } } +void LuaBackend::copy_locals(StateMemory* from, StateMemory* to) +{ + if (!local_state_datas.contains(from)) + return; + + auto& to_data = local_state_datas[to]; + auto& from_data = local_state_datas[from]; + to_data.state = from_data.state; + sol::object from_user_data = from_data.user_data; + if (from_user_data != sol::lua_nil) + { + to_data.user_data = (*vm)["deepcopy_object"](from_user_data); + } +} + void LuaBackend::pre_clone_heap(StateMemory* from, StateMemory* to) { if (!get_enabled()) return; - if (local_state_datas.contains(from)) { - sol::object from_data = local_state_datas[from].user_data; - if (from_data != sol::lua_nil) - { - local_state_datas[to].user_data = (*vm)["deepcopy_object"](from_data); - } - } + copy_locals(from, to); auto now = get_frame_count(); for (auto& [id, callback] : callbacks) { diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index 5781959e4..aff657893 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -289,6 +289,7 @@ struct RenderInfo; struct LocalStateData { sol::object user_data; + ScriptState state = {0, 0, 0, 0, 0, 0, 0, 0}; }; class LuaBackend @@ -307,7 +308,6 @@ class LuaBackend std::unordered_set loaded_modules; std::string result; - ScriptState state = {0, 0, 0, 0, 0, 0, 0, 0}; int cbcount = 0; CurrentCallback current_cb = {0, 0, CallbackType::None}; diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index 86a96bf58..e869557e4 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -634,6 +634,10 @@ uint32_t State::get_frame_count() const { return memory_read((size_t)ptr() - 0xd0); } +uint32_t State::get_frame_count(StateMemory* state) +{ + return memory_read((size_t)state - 0xd0); +} int64_t get_global_frame_count() { return global_frame_count; diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index 16a1666ee..d620c28f8 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -375,6 +375,7 @@ struct State uint32_t get_frame_count_main() const; uint32_t get_frame_count() const; + static uint32_t get_frame_count(StateMemory* state); std::vector read_prng() const; From 79c13f3463ff080ea8794326762637f01f492a1f Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Thu, 7 Mar 2024 22:58:01 -0300 Subject: [PATCH 06/16] format --- src/game_api/script/usertypes/state_lua.cpp | 31 +++++++++++---------- src/game_api/search.cpp | 2 +- src/game_api/state.cpp | 28 +++++++++---------- src/game_api/state.hpp | 1 + 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/game_api/script/usertypes/state_lua.cpp b/src/game_api/script/usertypes/state_lua.cpp index bc9b73ab1..f8a8bbb05 100644 --- a/src/game_api/script/usertypes/state_lua.cpp +++ b/src/game_api/script/usertypes/state_lua.cpp @@ -12,19 +12,19 @@ #include // for move, declval, decay_t, reference_... #include // for min, max -#include "entities_chars.hpp" // IWYU pragma: keep -#include "entity.hpp" // IWYU pragma: keep -#include "illumination.hpp" // IWYU pragma: keep -#include "items.hpp" // for Items, SelectPlayerSlot, Items::is... -#include "level_api.hpp" // IWYU pragma: keep -#include "online.hpp" // for OnlinePlayer, OnlineLobby, Online -#include "savestate.hpp" // for SaveState -#include "screen.hpp" // IWYU pragma: keep -#include "screen_arena.hpp" // IWYU pragma: keep -#include "script/events.hpp" // for pre_load_state -#include "state.hpp" // for StateMemory, State, StateMemory::a... -#include "state_structs.hpp" // for ArenaConfigArenas, ArenaConfigItems +#include "entities_chars.hpp" // IWYU pragma: keep +#include "entity.hpp" // IWYU pragma: keep +#include "illumination.hpp" // IWYU pragma: keep +#include "items.hpp" // for Items, SelectPlayerSlot, Items::is... +#include "level_api.hpp" // IWYU pragma: keep +#include "online.hpp" // for OnlinePlayer, OnlineLobby, Online +#include "savestate.hpp" // for SaveState +#include "screen.hpp" // IWYU pragma: keep +#include "screen_arena.hpp" // IWYU pragma: keep +#include "script/events.hpp" // for pre_load_state #include "script/lua_backend.hpp" // for LuaBackend +#include "state.hpp" // for StateMemory, State, StateMemory::a... +#include "state_structs.hpp" // for ArenaConfigArenas, ArenaConfigItems namespace NState { @@ -413,17 +413,18 @@ void register_usertypes(sol::state& lua) statememory_type["liquid"] = &StateMemory::liquid_physics; statememory_type["next_entity_uid"] = &StateMemory::next_entity_uid; statememory_type["room_owners"] = &StateMemory::room_owners; - + auto get_local_data = [](StateMemory& state) -> sol::object { auto backend = LuaBackend::get_calling_backend(); auto local_datas = backend->local_state_datas; - if (local_datas.contains(&state)) { + if (local_datas.contains(&state)) + { return local_datas[&state].user_data; } return sol::nil; }; - + auto set_local_data = [](StateMemory& state, sol::object user_data) -> void { auto backend = LuaBackend::get_calling_backend(); diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 503ff1d19..8f65a92b0 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -1833,7 +1833,7 @@ std::unordered_map g_address_rules{ .find_inst("4c 8d 05 f4 ca 27 00"_gh) .find_next_inst("eb 27"_gh) .offset(-0xC) - .at_exe() + .at_exe(), }, { // ^ writes to state.pause on state.loading == 3 diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index e869557e4..05d575af2 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -721,21 +721,21 @@ void init_state_clone_hook() { auto heap_clone = get_address("heap_clone"); // Hook the function after it has chosen a thread storage to write to, and pass it to the hook - size_t heap_clone_redirect_from_addr = heap_clone+0x65; + size_t heap_clone_redirect_from_addr = heap_clone + 0x65; const std::string redirect_code = fmt::format( - "\x51" // PUSH RCX - "\x52" // PUSH RDX - "\x41\x50" // PUSH R8 - "\x41\x51" // PUSH R9 - "\x48\x83\xEC\x28" // SUB RSP, 28 // Shadow space + Stack alignment - "\x4C\x89\xC9" // MOV RCX, R9 == heap_to - "\x48\xb8{}" // MOV RAX, &HeapClone - "\xff\xd0" // CALL RAX - "\x48\x83\xC4\x28" // ADD RSP, 28 - "\x41\x59" // POP R9 - "\x41\x58" // POP R8 - "\x5A" // POP RDX - "\x59"sv, // POP RCX + "\x51" // PUSH RCX + "\x52" // PUSH RDX + "\x41\x50" // PUSH R8 + "\x41\x51" // PUSH R9 + "\x48\x83\xEC\x28" // SUB RSP, 28 // Shadow space + Stack alignment + "\x4C\x89\xC9" // MOV RCX, R9 == heap_to + "\x48\xb8{}" // MOV RAX, &HeapClone + "\xff\xd0" // CALL RAX + "\x48\x83\xC4\x28" // ADD RSP, 28 + "\x41\x59" // POP R9 + "\x41\x58" // POP R8 + "\x5A" // POP RDX + "\x59"sv, // POP RCX to_le_bytes(&HeapClone)); patch_and_redirect(heap_clone_redirect_from_addr, 7, redirect_code, false, 0, false); diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index d620c28f8..6cc1ff851 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -389,6 +389,7 @@ struct State LiquidPhysicsEngine* get_correct_liquid_engine(ENT_TYPE liquid_type); size_t location; + private: State(size_t addr) : location(addr){}; From 43386cb3a7bdd4afac0147b6a00412a062e2c5ee Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Fri, 29 Mar 2024 00:26:44 -0300 Subject: [PATCH 07/16] docs, rename clone_heap -> copy_state, state local_data -> user_data --- docs/game_data/spel2.lua | 1 + docs/src/includes/_enums.md | 1 + docs/src/includes/_events.md | 7 +++++++ docs/src/includes/_types.md | 1 + src/game_api/script/events.cpp | 4 ++-- src/game_api/script/events.hpp | 2 +- src/game_api/script/lua_backend.cpp | 4 ++-- src/game_api/script/lua_backend.hpp | 5 ++--- src/game_api/script/lua_vm.cpp | 7 +++++-- src/game_api/script/usertypes/state_lua.cpp | 11 ++++++++--- src/game_api/state.cpp | 2 +- 11 files changed, 31 insertions(+), 14 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 4e6c9bf39..82cb8160d 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -2187,6 +2187,7 @@ do ---@field liquid LiquidPhysics ---@field next_entity_uid integer @Next entity spawned will have this uid ---@field room_owners RoomOwnersInfo @Holds info about owned rooms and items (shops, challenge rooms, vault etc.) + ---@field user_data any @user_data ---@class LightParams ---@field red number diff --git a/docs/src/includes/_enums.md b/docs/src/includes/_enums.md index c2b7f0caf..0375d7185 100644 --- a/docs/src/includes/_enums.md +++ b/docs/src/includes/_enums.md @@ -922,6 +922,7 @@ Name | Data | Description [BLOCKED_UPDATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_UPDATE) | ON::BLOCKED_UPDATE | Runs instead of POST_UPDATE when anything blocks a PRE_UPDATE. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_UPDATE.
[BLOCKED_GAME_LOOP](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_GAME_LOOP) | ON::BLOCKED_GAME_LOOP | Runs instead of POST_GAME_LOOP when anything blocks a PRE_GAME_LOOP. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_GAME_LOOP.
[BLOCKED_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_PROCESS_INPUT) | ON::BLOCKED_PROCESS_INPUT | Runs instead of POST_PROCESS_INPUT when anything blocks a PRE_PROCESS_INPUT. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_PROCESS_INPUT.
+[PRE_COPY_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_COPY_STATE) | ON::PRE_COPY_STATE | Runs before the game copies a state to another in an online multiplayer game.
Params: [StateMemory](#StateMemory) from, [StateMemory](#StateMemory) to
## PARTICLEEMITTER diff --git a/docs/src/includes/_events.md b/docs/src/includes/_events.md index d03a84b5c..047592b61 100644 --- a/docs/src/includes/_events.md +++ b/docs/src/includes/_events.md @@ -682,3 +682,10 @@ Runs instead of POST_GAME_LOOP when anything blocks a PRE_GAME_LOOP. Even runs i > Search script examples for [ON.BLOCKED_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_PROCESS_INPUT) Runs instead of POST_PROCESS_INPUT when anything blocks a PRE_PROCESS_INPUT. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_PROCESS_INPUT.
+ +## ON.PRE_COPY_STATE + + +> Search script examples for [ON.PRE_COPY_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_COPY_STATE) + +Runs before the game copies a state to another in an online multiplayer game.
Params: [StateMemory](#StateMemory) from, [StateMemory](#StateMemory) to
diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index d86cefc32..84ebaa68e 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -3084,6 +3084,7 @@ array<[THEME](#THEME), 9> | [journal_progress_theme_slots](https://github. [LiquidPhysics](#LiquidPhysics) | [liquid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=liquid) | int | [next_entity_uid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=next_entity_uid) | Next entity spawned will have this uid [RoomOwnersInfo](#RoomOwnersInfo) | [room_owners](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=room_owners) | Holds info about owned rooms and items (shops, challenge rooms, vault etc.) + | [user_data](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=user_data) | You can put any arbitrary lua object here and it will work correctly in online multiplayer, by having a copy on each state and being copied when the game does.
## Texture types diff --git a/src/game_api/script/events.cpp b/src/game_api/script/events.cpp index abfddb2ab..a39e0b241 100644 --- a/src/game_api/script/events.cpp +++ b/src/game_api/script/events.cpp @@ -518,12 +518,12 @@ void post_event(ON event) }); } -void pre_heap_clone_event(StateMemory* from, StateMemory* to) +void pre_copy_state_event(StateMemory* from, StateMemory* to) { LuaBackend::for_each_backend( [&](LuaBackend::LockedBackend backend) { - backend->pre_clone_heap(from, to); + backend->pre_copy_state(from, to); return true; }); } diff --git a/src/game_api/script/events.hpp b/src/game_api/script/events.hpp index 43be92749..e1896f797 100644 --- a/src/game_api/script/events.hpp +++ b/src/game_api/script/events.hpp @@ -26,13 +26,13 @@ bool pre_unload_level(); bool pre_unload_layer(LAYER layer); bool pre_save_state(int slot, StateMemory* saved); bool pre_load_state(int slot, StateMemory* loaded); +void pre_copy_state_event(StateMemory* from, StateMemory* to); void post_load_screen(); void post_init_layer(LAYER layer); void post_unload_layer(LAYER layer); void post_room_generation(); void post_level_generation(); -void pre_heap_clone_event(StateMemory* from, StateMemory* to); void post_save_state(int slot, StateMemory* saved); void post_load_state(int slot, StateMemory* loaded); diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 217f906b6..cd6e1d70a 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -1892,7 +1892,7 @@ void LuaBackend::copy_locals(StateMemory* from, StateMemory* to) } } -void LuaBackend::pre_clone_heap(StateMemory* from, StateMemory* to) +void LuaBackend::pre_copy_state(StateMemory* from, StateMemory* to) { if (!get_enabled()) return; @@ -1904,7 +1904,7 @@ void LuaBackend::pre_clone_heap(StateMemory* from, StateMemory* to) if (is_callback_cleared(id)) continue; - if (callback.screen == ON::PRE_CLONE_HEAP) + if (callback.screen == ON::PRE_COPY_STATE) { set_current_callback(-1, id, CallbackType::Normal); handle_function(this, callback.func, from, to); diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index aff657893..8c0292898 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -139,8 +139,7 @@ enum class ON BLOCKED_UPDATE, BLOCKED_GAME_LOOP, BLOCKED_PROCESS_INPUT, - PRE_CLONE_HEAP, - POST_CLONE_HEAP, + PRE_COPY_STATE, }; struct IntOption @@ -468,7 +467,7 @@ class LuaBackend void load_user_data(); bool on_pre(ON event); void on_post(ON event); - void pre_clone_heap(StateMemory* from, StateMemory* to); + void pre_copy_state(StateMemory* from, StateMemory* to); void hotkey_callback(int cb); int register_hotkey(HotKeyCallback cb, HOTKEY_TYPE flags); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 27fbe72a9..baa602d91 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2465,8 +2465,8 @@ end ON::BLOCKED_GAME_LOOP, "BLOCKED_PROCESS_INPUT", ON::BLOCKED_PROCESS_INPUT, - "PRE_CLONE_HEAP", - ON::PRE_CLONE_HEAP); + "PRE_COPY_STATE", + ON::PRE_COPY_STATE); /* ON // LOGO @@ -2723,6 +2723,9 @@ end // Runs instead of POST_GAME_LOOP when anything blocks a PRE_GAME_LOOP. Even runs in Playlunky when Overlunky blocks a PRE_GAME_LOOP. // BLOCKED_PROCESS_INPUT // Runs instead of POST_PROCESS_INPUT when anything blocks a PRE_PROCESS_INPUT. Even runs in Playlunky when Overlunky blocks a PRE_PROCESS_INPUT. + // PRE_COPY_STATE + // Runs before the game copies a state to another in an online multiplayer game. + // Params: StateMemory from, StateMemory to */ lua.create_named_table( diff --git a/src/game_api/script/usertypes/state_lua.cpp b/src/game_api/script/usertypes/state_lua.cpp index f8a8bbb05..3a9346e45 100644 --- a/src/game_api/script/usertypes/state_lua.cpp +++ b/src/game_api/script/usertypes/state_lua.cpp @@ -414,7 +414,7 @@ void register_usertypes(sol::state& lua) statememory_type["next_entity_uid"] = &StateMemory::next_entity_uid; statememory_type["room_owners"] = &StateMemory::room_owners; - auto get_local_data = [](StateMemory& state) -> sol::object + auto state_get_user_data = [](StateMemory& state) -> sol::object { auto backend = LuaBackend::get_calling_backend(); auto local_datas = backend->local_state_datas; @@ -425,13 +425,18 @@ void register_usertypes(sol::state& lua) return sol::nil; }; - auto set_local_data = [](StateMemory& state, sol::object user_data) -> void + auto state_set_user_data = [](StateMemory& state, sol::object user_data) -> void { auto backend = LuaBackend::get_calling_backend(); backend->local_state_datas[&state].user_data = user_data; }; + auto user_data = sol::property(state_get_user_data, state_set_user_data); - statememory_type["local_data"] = sol::property(get_local_data, set_local_data); + statememory_type["user_data"] = std::move(user_data); + /* StateMemory + // user_data + // You can put any arbitrary lua object here and it will work correctly in online multiplayer, by having a copy on each state and being copied when the game does. + */ lua.create_named_table("FADE", "NONE", 0, "OUT", 1, "LOAD", 2, "IN", 3); diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index 05d575af2..0b6dbfcc1 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -710,7 +710,7 @@ void HeapClone(uint64_t heap_to, uint64_t heap_container_from) uint64_t location = memory_read(State::get().location); StateMemory* state_from = reinterpret_cast(memory_read(heap_container_from + 0x88) + location); StateMemory* state_to = reinterpret_cast(heap_to + location); - pre_heap_clone_event(state_from, state_to); + pre_copy_state_event(state_from, state_to); } // Original function params: clone_heap(ThreadStorageContainer to, ThreadStorageContainer from) From 8df6529ae76b1046c1aee83a41a0a44db8c93dba Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Sun, 31 Mar 2024 00:08:10 -0300 Subject: [PATCH 08/16] change state location to read-only via getter --- src/game_api/state.cpp | 7 ++++++- src/game_api/state.hpp | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index 0b6dbfcc1..c0d39daec 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -626,6 +626,11 @@ LiquidPhysicsEngine* State::get_correct_liquid_engine(ENT_TYPE liquid_type) return nullptr; } +size_t State::get_location() +{ + return this->location; +} + uint32_t State::get_frame_count_main() const { return memory_read((size_t)ptr_main() - 0xd0); @@ -707,7 +712,7 @@ void init_state_update_hook() void HeapClone(uint64_t heap_to, uint64_t heap_container_from) { - uint64_t location = memory_read(State::get().location); + uint64_t location = memory_read(State::get().get_location()); StateMemory* state_from = reinterpret_cast(memory_read(heap_container_from + 0x88) + location); StateMemory* state_to = reinterpret_cast(heap_to + location); pre_copy_state_event(state_from, state_to); diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index 6cc1ff851..f3102a842 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -387,13 +387,13 @@ struct State void set_seed(uint32_t seed); SaveData* savedata(); LiquidPhysicsEngine* get_correct_liquid_engine(ENT_TYPE liquid_type); - - size_t location; + size_t get_location(); private: State(size_t addr) : location(addr){}; + size_t location; State(const State&) = delete; State& operator=(const State&) = delete; }; From 488d64301781121c8a49d66e1314e2dd59c7f614 Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Wed, 10 Apr 2024 02:17:33 -0300 Subject: [PATCH 09/16] trigger PRE_COPY_STATE on Overlunky savestates --- src/game_api/savestate.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/game_api/savestate.cpp b/src/game_api/savestate.cpp index 6b8823fe6..4a2770e64 100644 --- a/src/game_api/savestate.cpp +++ b/src/game_api/savestate.cpp @@ -13,11 +13,20 @@ size_t get_state_offset() return 0x4a0; } +StateMemory* get_save_state_raw(int slot) +{ + size_t arr = get_address("save_states"); + size_t base = memory_read(arr + (slot - 1) * 8); + auto state = reinterpret_cast(base + get_state_offset()); + return state; +} + void copy_save_slot(int from, int to) { if ((from == 5 && pre_save_state(to, get_save_state(to))) || (to == 5 && pre_load_state(from, get_save_state(from)))) return; + pre_copy_state_event(get_save_state_raw(from), get_save_state_raw(to)); size_t arr = get_address("save_states"); size_t fromBaseState = memory_read(arr + (from - 1) * 8); size_t toBaseState = memory_read(arr + (to - 1) * 8); @@ -57,9 +66,7 @@ void copy_state(size_t fromBaseState, size_t toBaseState) StateMemory* get_save_state(int slot) { - size_t arr = get_address("save_states"); - size_t base = memory_read(arr + (slot - 1) * 8); - auto state = reinterpret_cast(base + get_state_offset()); + auto state = get_save_state_raw(slot); if (state->screen) return state; return nullptr; From c70423666ccf1b4b452c0fe498956e392a62db13 Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Wed, 10 Apr 2024 17:17:23 -0300 Subject: [PATCH 10/16] move State::get_location to header file --- src/game_api/state.cpp | 5 ----- src/game_api/state.hpp | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index c0d39daec..640bc46d4 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -626,11 +626,6 @@ LiquidPhysicsEngine* State::get_correct_liquid_engine(ENT_TYPE liquid_type) return nullptr; } -size_t State::get_location() -{ - return this->location; -} - uint32_t State::get_frame_count_main() const { return memory_read((size_t)ptr_main() - 0xd0); diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index f3102a842..f6dd206b7 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -387,7 +387,10 @@ struct State void set_seed(uint32_t seed); SaveData* savedata(); LiquidPhysicsEngine* get_correct_liquid_engine(ENT_TYPE liquid_type); - size_t get_location(); + size_t get_location() + { + return this->location; + } private: State(size_t addr) From 3881f061ccf6dd670e3df7147873b54ff2b9875a Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Thu, 11 Apr 2024 20:56:25 -0300 Subject: [PATCH 11/16] minor autodoc fix --- docs/game_data/spel2.lua | 2 +- docs/src/includes/_types.md | 2 +- src/game_api/state.hpp | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 82cb8160d..12a867b29 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -2187,7 +2187,7 @@ do ---@field liquid LiquidPhysics ---@field next_entity_uid integer @Next entity spawned will have this uid ---@field room_owners RoomOwnersInfo @Holds info about owned rooms and items (shops, challenge rooms, vault etc.) - ---@field user_data any @user_data + ---@field user_data any ---@class LightParams ---@field red number diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index 84ebaa68e..30ade8e96 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -3084,7 +3084,7 @@ array<[THEME](#THEME), 9> | [journal_progress_theme_slots](https://github. [LiquidPhysics](#LiquidPhysics) | [liquid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=liquid) | int | [next_entity_uid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=next_entity_uid) | Next entity spawned will have this uid [RoomOwnersInfo](#RoomOwnersInfo) | [room_owners](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=room_owners) | Holds info about owned rooms and items (shops, challenge rooms, vault etc.) - | [user_data](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=user_data) | You can put any arbitrary lua object here and it will work correctly in online multiplayer, by having a copy on each state and being copied when the game does.
+any | [user_data](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=user_data) | You can put any arbitrary lua object here and it will work correctly in online multiplayer, by having a copy on each state and being copied when the game does.
## Texture types diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index f6dd206b7..96380ed8d 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -310,6 +310,10 @@ struct StateMemory uint8_t unknown43; uint32_t unknown44; // probably padding + /* for the autodoc + any user_data; + */ + /// This function should only be used in a very specific circumstance (forcing the exiting theme when manually transitioning). Will crash the game if used inappropriately! void force_current_theme(THEME t); From 7e9c411bca4c5eff65e1eb491919f0123379d41a Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Tue, 30 Apr 2024 22:44:04 -0300 Subject: [PATCH 12/16] remove PRE_COPY_STATE lua event for now --- docs/src/includes/_enums.md | 1 - docs/src/includes/_events.md | 7 ------- src/game_api/script/lua_backend.cpp | 28 ++++++++++++++-------------- src/game_api/script/lua_backend.hpp | 2 +- src/game_api/script/lua_vm.cpp | 7 +------ 5 files changed, 16 insertions(+), 29 deletions(-) diff --git a/docs/src/includes/_enums.md b/docs/src/includes/_enums.md index 0375d7185..c2b7f0caf 100644 --- a/docs/src/includes/_enums.md +++ b/docs/src/includes/_enums.md @@ -922,7 +922,6 @@ Name | Data | Description [BLOCKED_UPDATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_UPDATE) | ON::BLOCKED_UPDATE | Runs instead of POST_UPDATE when anything blocks a PRE_UPDATE. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_UPDATE.
[BLOCKED_GAME_LOOP](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_GAME_LOOP) | ON::BLOCKED_GAME_LOOP | Runs instead of POST_GAME_LOOP when anything blocks a PRE_GAME_LOOP. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_GAME_LOOP.
[BLOCKED_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_PROCESS_INPUT) | ON::BLOCKED_PROCESS_INPUT | Runs instead of POST_PROCESS_INPUT when anything blocks a PRE_PROCESS_INPUT. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_PROCESS_INPUT.
-[PRE_COPY_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_COPY_STATE) | ON::PRE_COPY_STATE | Runs before the game copies a state to another in an online multiplayer game.
Params: [StateMemory](#StateMemory) from, [StateMemory](#StateMemory) to
## PARTICLEEMITTER diff --git a/docs/src/includes/_events.md b/docs/src/includes/_events.md index 047592b61..d03a84b5c 100644 --- a/docs/src/includes/_events.md +++ b/docs/src/includes/_events.md @@ -682,10 +682,3 @@ Runs instead of POST_GAME_LOOP when anything blocks a PRE_GAME_LOOP. Even runs i > Search script examples for [ON.BLOCKED_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_PROCESS_INPUT) Runs instead of POST_PROCESS_INPUT when anything blocks a PRE_PROCESS_INPUT. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_PROCESS_INPUT.
- -## ON.PRE_COPY_STATE - - -> Search script examples for [ON.PRE_COPY_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_COPY_STATE) - -Runs before the game copies a state to another in an online multiplayer game.
Params: [StateMemory](#StateMemory) from, [StateMemory](#StateMemory) to
diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index cd6e1d70a..ecc91579f 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -1898,20 +1898,20 @@ void LuaBackend::pre_copy_state(StateMemory* from, StateMemory* to) return; copy_locals(from, to); - auto now = get_frame_count(); - for (auto& [id, callback] : callbacks) - { - if (is_callback_cleared(id)) - continue; - - if (callback.screen == ON::PRE_COPY_STATE) - { - set_current_callback(-1, id, CallbackType::Normal); - handle_function(this, callback.func, from, to); - clear_current_callback(); - callback.lastRan = now; - } - } + // auto now = get_frame_count(); + // for (auto& [id, callback] : callbacks) + // { + // if (is_callback_cleared(id)) + // continue; + + // if (callback.screen == ON::PRE_COPY_STATE) + // { + // set_current_callback(-1, id, CallbackType::Normal); + // handle_function(this, callback.func, from, to); + // clear_current_callback(); + // callback.lastRan = now; + // } + // } } bool LuaBackend::pre_save_state(int slot, StateMemory* saved) { diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index 8c0292898..85c8dcdee 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -139,7 +139,7 @@ enum class ON BLOCKED_UPDATE, BLOCKED_GAME_LOOP, BLOCKED_PROCESS_INPUT, - PRE_COPY_STATE, + // PRE_COPY_STATE, }; struct IntOption diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index baa602d91..24aef8586 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2464,9 +2464,7 @@ end "BLOCKED_GAME_LOOP", ON::BLOCKED_GAME_LOOP, "BLOCKED_PROCESS_INPUT", - ON::BLOCKED_PROCESS_INPUT, - "PRE_COPY_STATE", - ON::PRE_COPY_STATE); + ON::BLOCKED_PROCESS_INPUT); /* ON // LOGO @@ -2723,9 +2721,6 @@ end // Runs instead of POST_GAME_LOOP when anything blocks a PRE_GAME_LOOP. Even runs in Playlunky when Overlunky blocks a PRE_GAME_LOOP. // BLOCKED_PROCESS_INPUT // Runs instead of POST_PROCESS_INPUT when anything blocks a PRE_PROCESS_INPUT. Even runs in Playlunky when Overlunky blocks a PRE_PROCESS_INPUT. - // PRE_COPY_STATE - // Runs before the game copies a state to another in an online multiplayer game. - // Params: StateMemory from, StateMemory to */ lua.create_named_table( From f105e884e094937766390c16cd5043e790c783d5 Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Fri, 3 May 2024 01:09:59 -0300 Subject: [PATCH 13/16] fix get_local_* return type in docs --- docs/game_data/spel2.lua | 6 +++--- docs/src/includes/_globals.md | 6 +++--- src/game_api/script/lua_vm.cpp | 4 ++-- src/game_api/script/usertypes/prng_lua.cpp | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 12a867b29..8b5151a59 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1207,10 +1207,10 @@ function set_level_string(str) end ---@return nil function set_ending_unlock(type) end ---Get the thread-local version of state ----@return nil +---@return StateMemory function get_local_state() end ---Get the thread-local version of players ----@return nil +---@return Player[] function get_local_players() end ---List files in directory relative to the script root. Returns table of file/directory names or nil if not found. ---@param dir string? @@ -1410,7 +1410,7 @@ function get_raw_input() end ---@return nil function seed_prng(seed) end ---Get the thread-local version of prng ----@return nil +---@return PRNG function get_local_prng() end ---Same as `Player.get_name` ---@param type_id ENT_TYPE diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 15054eb45..b038e9949 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -714,7 +714,7 @@ Gets a grid entity, such as floor or spikes, at the given position and layer. > Search script examples for [get_local_players](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_local_players) -#### nil get_local_players() +#### vector<[Player](#Player)> get_local_players() Get the thread-local version of players @@ -1484,7 +1484,7 @@ Gets the value for the specified config > Search script examples for [get_local_prng](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_local_prng) -#### nil get_local_prng() +#### [PRNG](#PRNG) get_local_prng() Get the thread-local version of prng @@ -1493,7 +1493,7 @@ Get the thread-local version of prng > Search script examples for [get_local_state](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_local_state) -#### nil get_local_state() +#### [StateMemory](#StateMemory) get_local_state() Get the thread-local version of state diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 24aef8586..bb1ebfcbf 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -1984,13 +1984,13 @@ end lua["set_ending_unlock"] = set_ending_unlock; /// Get the thread-local version of state - lua["get_local_state"] = []() + lua["get_local_state"] = []() -> StateMemory* { return State::get().ptr_local(); }; /// Get the thread-local version of players - lua["get_local_players"] = []() + lua["get_local_players"] = []() -> std::vector { return get_players(State::get().ptr_local()); }; diff --git a/src/game_api/script/usertypes/prng_lua.cpp b/src/game_api/script/usertypes/prng_lua.cpp index 3e1955380..78405f199 100644 --- a/src/game_api/script/usertypes/prng_lua.cpp +++ b/src/game_api/script/usertypes/prng_lua.cpp @@ -51,7 +51,7 @@ void register_usertypes(sol::state& lua) lua["prng"] = &PRNG::get_main(); /// Get the thread-local version of prng - lua["get_local_prng"] = []() + lua["get_local_prng"] = []() -> PRNG* { return &PRNG::get_local(); }; From f93ee54120bc70b1108a81a80c1c03b79c101724 Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Sun, 2 Jun 2024 23:08:21 -0300 Subject: [PATCH 14/16] fix spawn_api and custom_allocator online g_SpawnTypeFlags is now SYSTEMIC by default because when some threads run spawns for the first times, the SYSTEMIC flag may not be set, causing desyncs online --- src/game_api/containers/custom_allocator.cpp | 6 ++++-- src/game_api/spawn_api.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/game_api/containers/custom_allocator.cpp b/src/game_api/containers/custom_allocator.cpp index 8237b9a2e..beb631870 100644 --- a/src/game_api/containers/custom_allocator.cpp +++ b/src/game_api/containers/custom_allocator.cpp @@ -12,12 +12,14 @@ using CustomFreeFun = void*(void*, void*); void* custom_malloc(std::size_t size) { static CustomMallocFun* _malloc = (CustomMallocFun*)get_address("custom_malloc"sv); - static void* _alloc_base = OnHeapPointer(*(size_t*)get_address("malloc_base"sv)).decode(); + static size_t _heap_ptr_malloc_base = *reinterpret_cast(get_address("malloc_base"sv)); + void* _alloc_base = OnHeapPointer(_heap_ptr_malloc_base).decode_local(); return _malloc(_alloc_base, size); } void custom_free(void* mem) { static CustomFreeFun* _free = (CustomFreeFun*)get_address("custom_free"sv); - static void* _alloc_base = OnHeapPointer(*(size_t*)get_address("malloc_base"sv)).decode(); + static size_t _heap_ptr_malloc_base = *reinterpret_cast(get_address("malloc_base"sv)); + void* _alloc_base = OnHeapPointer(_heap_ptr_malloc_base).decode_local(); _free(_alloc_base, mem); } diff --git a/src/game_api/spawn_api.cpp b/src/game_api/spawn_api.cpp index 28013a6dd..44311d817 100644 --- a/src/game_api/spawn_api.cpp +++ b/src/game_api/spawn_api.cpp @@ -35,10 +35,10 @@ struct Items; -std::uint32_t g_SpawnNonReplacable; -SpawnType g_SpawnTypeFlags; -std::array g_SpawnTypes{}; -std::function g_temp_entity_spawn_hook; +thread_local std::uint32_t g_SpawnNonReplacable; +thread_local SpawnType g_SpawnTypeFlags{SPAWN_TYPE_SYSTEMIC}; +thread_local std::array g_SpawnTypes{}; +thread_local std::function g_temp_entity_spawn_hook; void spawn_liquid(ENT_TYPE entity_type, float x, float y) { From 98cb792975b1b69c33d826c2237158b1d7add2e1 Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Sat, 3 Aug 2024 00:36:08 -0300 Subject: [PATCH 15/16] better deepcopy_object implementation --- docs/src/includes/_types.md | 2 +- src/game_api/script/lua_backend.cpp | 45 +++++++++++++++++++-- src/game_api/script/lua_vm.cpp | 10 ----- src/game_api/script/usertypes/state_lua.cpp | 3 +- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index ed81ad80d..d89d35a25 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -3115,7 +3115,7 @@ array<[THEME](#THEME), 9> | [journal_progress_theme_slots](https://github. [LiquidPhysics](#LiquidPhysics) | [liquid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=liquid) | int | [next_entity_uid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=next_entity_uid) | Next entity spawned will have this uid [RoomOwnersInfo](#RoomOwnersInfo) | [room_owners](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=room_owners) | Holds info about owned rooms and items (shops, challenge rooms, vault etc.) -any | [user_data](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=user_data) | You can put any arbitrary lua object here and it will work correctly in online multiplayer, by having a copy on each state and being copied when the game does.
+any | [user_data](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=user_data) | You can store a table (or lua primitive) here and it will store data correctly in online multiplayer, by having a different copy on each state and being copied over when the game does.
Doesn't support recursive tables / cyclic references. Metatables will be transferred by reference instead of being copied
## Texture types diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 26d512a29..3bf95bd4e 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -6,9 +6,10 @@ #include // for format_error #include // for _List_iterator, _List_co... #include // for table_proxy, optional -#include // for stack -#include // for get -#include // for vector +#include +#include // for stack +#include // for get +#include // for vector #include "aliases.hpp" // for IMAGE, JournalPageType #include "bucket.hpp" // for Bucket @@ -1871,6 +1872,42 @@ void LuaBackend::on_post(ON event) } } +sol::table deepcopy_lua_table(sol::state& sol_state, sol::table& from_r) +{ + sol::table new_table(sol_state, sol::create); + for (auto& [k, v] : from_r.as()) + { + if (v.is()) + { + sol::table v_table = v.as(); + new_table.raw_set(k, deepcopy_lua_table(sol_state, v_table)); + } + else + { + new_table.raw_set(k, v); + } + } + auto maybe_metatable = from_r.raw_get>(sol::metatable_key); + if (maybe_metatable) + { + new_table.raw_set(sol::metatable_key, maybe_metatable.value()); + } + return new_table; +} + +inline sol::object deepcopy_lua(sol::state& sol_state, sol::object& from) +{ + if (from.is()) + { + auto from_t = from.as(); + return deepcopy_lua_table(sol_state, from_t); + } + else + { + return from; + } +} + void LuaBackend::copy_locals(StateMemory* from, StateMemory* to) { if (!local_state_datas.contains(from)) @@ -1882,7 +1919,7 @@ void LuaBackend::copy_locals(StateMemory* from, StateMemory* to) sol::object from_user_data = from_data.user_data; if (from_user_data != sol::lua_nil) { - to_data.user_data = (*vm)["deepcopy_object"](from_user_data); + to_data.user_data = deepcopy_lua(*vm, from_user_data); } } diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index bbd838c93..bce5d5172 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2266,16 +2266,6 @@ end /// Initializes some seedeed run related values and loads the character select screen, as if starting a new seeded run after entering the seed. lua["play_seeded"] = init_seeded; - lua.script(R"##( - function deepcopy_object(obj) - if type(obj) ~= 'table' then return obj end - local res = {} - for k, v in pairs(obj) do res[deepcopy_object(k)] = deepcopy_object(v) end - res = setmetatable(res, getmetatable(obj)) - return res - end - )##"); - /// Change layer at which the liquid spawns in, THIS FUNCTION NEEDS TO BE CALLED BEFORE THE LEVEL IS BUILD, otherwise collisions and other stuff will be wrong for the newly spawned liquid /// This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or place them directly in level files) /// Everything should be working more or less correctly (report on community discord if you find something unusual) diff --git a/src/game_api/script/usertypes/state_lua.cpp b/src/game_api/script/usertypes/state_lua.cpp index b2e6d3f09..1a90210a0 100644 --- a/src/game_api/script/usertypes/state_lua.cpp +++ b/src/game_api/script/usertypes/state_lua.cpp @@ -436,7 +436,8 @@ void register_usertypes(sol::state& lua) statememory_type["user_data"] = std::move(user_data); /* StateMemory // user_data - // You can put any arbitrary lua object here and it will work correctly in online multiplayer, by having a copy on each state and being copied when the game does. + // You can store a table (or lua primitive) here and it will store data correctly in online multiplayer, by having a different copy on each state and being copied over when the game does. + // Doesn't support recursive tables / cyclic references. Metatables will be transferred by reference instead of being copied */ lua.create_named_table("FADE", "NONE", 0, "OUT", 1, "LOAD", 2, "IN", 3); From 9f702353e564bb770698656596411d7e05e9914c Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Sat, 3 Aug 2024 20:48:07 -0300 Subject: [PATCH 16/16] change get_location to get_offset, remove get_state_offset --- src/game_api/savestate.cpp | 24 ++++++++++-------------- src/game_api/state.cpp | 2 +- src/game_api/state.hpp | 6 ++++-- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/game_api/savestate.cpp b/src/game_api/savestate.cpp index 8bac874a4..2774d784c 100644 --- a/src/game_api/savestate.cpp +++ b/src/game_api/savestate.cpp @@ -5,19 +5,11 @@ #include "script/events.hpp" // for pre_load_state #include "state.hpp" // for State, get_state_ptr, enum_to_layer -size_t get_state_offset() -{ - auto addr = get_address("state_location"); - if (addr) - return memory_read(addr); - return 0x4a0; -} - StateMemory* get_save_state_raw(int slot) { size_t arr = get_address("save_states"); size_t base = memory_read(arr + (slot - 1) * 8); - auto state = reinterpret_cast(base + get_state_offset()); + auto state = reinterpret_cast(base + State::get().get_offset()); return state; } @@ -95,15 +87,17 @@ StateMemory* SaveState::get_state() const { if (!addr) return nullptr; - return reinterpret_cast(addr + get_state_offset()); + return reinterpret_cast(addr + State::get().get_offset()); } void SaveState::load() { if (!addr) return; - size_t to = (size_t)(State::get().ptr_main()) - get_state_offset(); - auto state = reinterpret_cast(addr + get_state_offset()); + State& state_g = State::get(); + size_t offset = state_g.get_offset(); + size_t to = (size_t)(state_g.ptr_main()) - offset; + auto state = reinterpret_cast(addr + offset); if (pre_load_state(-1, state)) return; copy_state(addr, to); @@ -114,8 +108,10 @@ void SaveState::save() { if (!addr) return; - size_t from = (size_t)(State::get().ptr_main()) - get_state_offset(); - auto state = reinterpret_cast(addr + get_state_offset()); + State& state_g = State::get(); + size_t offset = state_g.get_offset(); + size_t from = (size_t)(state_g.ptr_main()) - offset; + auto state = reinterpret_cast(addr + offset); if (pre_save_state(-1, state)) return; copy_state(from, addr); diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index 0cefbfa38..003141c9a 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -699,7 +699,7 @@ void init_state_update_hook() void HeapClone(uint64_t heap_to, uint64_t heap_container_from) { - uint64_t location = memory_read(State::get().get_location()); + uint64_t location = State::get().get_offset(); StateMemory* state_from = reinterpret_cast(memory_read(heap_container_from + 0x88) + location); StateMemory* state_to = reinterpret_cast(heap_to + location); pre_copy_state_event(state_from, state_to); diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index 73bde2c2e..a76663e48 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -8,6 +8,7 @@ #include "aliases.hpp" // for ENT_TYPE, LAYER #include "containers/custom_vector.hpp" // +#include "memory.hpp" // for memory_read #include "state_structs.hpp" // for JournalProgressStickerSlot, ... class Entity; @@ -397,9 +398,10 @@ struct State void set_seed(uint32_t seed); SaveData* savedata(); LiquidPhysicsEngine* get_correct_liquid_engine(ENT_TYPE liquid_type) const; - size_t get_location() + // Get the 0x4A0 offset + size_t get_offset() const { - return this->location; + return memory_read(location); } private: