diff --git a/README.md b/README.md index 5eb14c5..904866d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,19 @@ Read `lua/gcrash.lua` for information on function behavior. The dump format is a simple text file containing a stack trace and any extra data generated by the crash handler. Dump files are saved under `garrysmod/gcrash/` with a name containing the date and time of the crash. +### UPDATE ~ x64 - 2025 + +Update on x64 Builds for GCrash +Testing the x64 builds on Linux proved to be unstable and resulted in random crashes. As a result, I've decided to simplify the x64 version of GCrash to focus solely on the Watchdog feature (freeze/hang detection and auto-restart). + +As of 2025, Garry's Mod now supports dumping its state to text files. While this feature isn't as comprehensive as GCrash's stack dumps, it helps retain useful information. Additionally, Garry's Mod offers a console buffer for error logs, which can provide further insights in case of crashes. + +With this in mind, I've removed the full GCrash functionality from the x64 build and kept only the infinite loop and server freeze detection, with automatic restart. This change is aimed at improving stability for x64 servers. + +----------------------------- + +Please note that if you're re-compiling, you’ll need to adjust the source files accordingly. The x86 version will remain unchanged and retain all previous features. + ## Installing Get the released (or build from source) `gmsv_gcrash_linux.dll` and put it in diff --git a/libs/bin/linux64/lua_shared.so b/libs/bin/linux64/lua_shared.so new file mode 100644 index 0000000..e285b5c Binary files /dev/null and b/libs/bin/linux64/lua_shared.so differ diff --git a/libs/garrysmod/bin/lua_shared.so b/libs/garrysmod/bin/lua_shared.so deleted file mode 100644 index 1333a27..0000000 Binary files a/libs/garrysmod/bin/lua_shared.so and /dev/null differ diff --git a/libs/garrysmod/bin/lua_shared_srv.so b/libs/garrysmod/bin/lua_shared_srv.so new file mode 100644 index 0000000..f761130 Binary files /dev/null and b/libs/garrysmod/bin/lua_shared_srv.so differ diff --git a/libs/lua_shared.dll b/libs/lua_shared.dll index 2325b5e..b9d2914 100644 Binary files a/libs/lua_shared.dll and b/libs/lua_shared.dll differ diff --git a/lua/gcrash.lua b/lua/gcrash.lua index 15bf77c..7725425 100644 --- a/lua/gcrash.lua +++ b/lua/gcrash.lua @@ -1,3 +1,4 @@ +if CLIENT then return end require "gcrash" local enable_watchdog = true @@ -24,32 +25,32 @@ local enable_watchdog = true Destroy the watchdog, this is done automatically on server stop. ]] -gcrash.sethandler( function( write ) - local function printline( s, ... ) write( string.format( tostring( s or "" ) .. "\n", ... ) ) end +gcrash.sethandler(function(write) + local function printline(s, ...) write(string.format(tostring(s or "") .. "\n", ...)) end local i = 2 -- Only start at frame 2, we don't need the locals of this function - local dbg = debug.getinfo( i ) + local dbg = debug.getinfo(i) while dbg do - printline( "Frame #%d:", i - 2 ) + printline("Frame #%d:", i - 2) for j = 1, 255 do - local n, v = debug.getlocal( i, j ) + local n, v = debug.getlocal(i, j) if not n then break end - printline( " %s = %s", tostring( n ), tostring( v ) ) + printline(" %s = %s", tostring(n), tostring(v)) end - printline( "" ) + printline("") i = i + 1 - dbg = debug.getinfo( i ) + dbg = debug.getinfo(i) end -end ) +end) gcrash.crash = nil -- You can comment this out if you want to use it (to crash your server?) if enable_watchdog then - timer.Simple(30, function() - gcrash.startwatchdog() - print( "Starting gcrash watchdog..." ) + RunConsoleCommand("sv_hibernate_think", 1) -- We need it to run the watchdog updater while the server is empty + timer.Simple(120, function() -- We let the watchdog wait before launching for long time loading servers + gcrash.startwatchdog(45) end) -end \ No newline at end of file +end diff --git a/premake5.lua b/premake5.lua index d22461a..2b79faa 100644 --- a/premake5.lua +++ b/premake5.lua @@ -9,7 +9,7 @@ project "gmsv_gcrash" libdirs "libs/" targetdir "build" - if _ACTION == "gmake2" then + if _ACTION == "gmake" then postbuildcommands "{MOVE} %{cfg.targetdir}/lib%{prj.name}%{cfg.targetsuffix}.so %{cfg.targetdir}/%{prj.name}%{cfg.targetsuffix}.dll" else links "lua_shared" @@ -21,46 +21,44 @@ project "gmsv_gcrash" "include/**.*" } - configuration "Debug" + filter "configurations:Debug" architecture "x86" optimize "Debug" - if _ACTION == "gmake2" then - linkoptions "-l:garrysmod/bin/lua_shared.so" + if _ACTION == "gmake" then + linkoptions "-l:garrysmod/bin/lua_shared_srv.so" end if os.istarget( "windows" ) then targetsuffix "_win32" end if os.istarget( "macosx" ) then targetsuffix "_osx" end if os.istarget( "linux" ) then targetsuffix "_linux" end - configuration "Debug64" + filter "configurations:Debug64" architecture "x86_64" optimize "Debug" - if _ACTION == "gmake2" then + if _ACTION == "gmake" then linkoptions "-l:bin/linux64/lua_shared.so" end if os.istarget( "windows" ) then targetsuffix "_win64" end if os.istarget( "macosx" ) then targetsuffix "_osx64" end if os.istarget( "linux" ) then targetsuffix "_linux64" end - configuration "Release" + filter "configurations:Release" architecture "x86" optimize "Speed" - if staticruntime then staticruntime "On" - else flags "StaticRuntime" end - if _ACTION == "gmake2" then - linkoptions "-l:garrysmod/bin/lua_shared.so" + staticruntime "On" + if _ACTION == "gmake" then + linkoptions "-l:garrysmod/bin/lua_shared_srv.so" end if os.istarget( "windows" ) then targetsuffix "_win32" end if os.istarget( "macosx" ) then targetsuffix "_osx" end if os.istarget( "linux" ) then targetsuffix "_linux" end - configuration "Release64" + filter "configurations:Release64" architecture "x86_64" optimize "Speed" - if staticruntime then staticruntime "On" - else flags "StaticRuntime" end - if _ACTION == "gmake2" then + staticruntime "On" + if _ACTION == "gmake" then linkoptions "-l:bin/linux64/lua_shared.so" end if os.istarget( "windows" ) then targetsuffix "_win64" end if os.istarget( "macosx" ) then targetsuffix "_osx64" end - if os.istarget( "linux" ) then targetsuffix "_linux64" end \ No newline at end of file + if os.istarget( "linux" ) then targetsuffix "_linux64" end diff --git a/src/main.cpp b/src/main.cpp index 63873b2..853128a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,251 +1,228 @@ - #include -#include #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include - #include "lua_headers.h" -lua_State* L; -int luahandler; - -int doprint( lua_State* state ) -{ - FILE* f = (FILE*) lua_topointer( state, lua_upvalueindex( 1 ) ); - fprintf( f, "%s", luaL_checkstring( state, 1 ) ); - return 0; +// Globals +lua_State* L = nullptr; +std::ofstream f; +int luahandler = 0; +int frozen = 0; + +int doprint(lua_State* state) { + if (f.is_open()) { + f << luaL_checkstring(state, 1); + f.flush(); + } + return 0; } -void print_handler( FILE* f, lua_State* state ) -{ - if ( !luahandler ) return; - fprintf( f, "\nLua crash handler:\n\n" ); - lua_rawgeti( state, LUA_REGISTRYINDEX, luahandler ); - lua_pushlightuserdata( state, (void*) f ); - lua_pushcclosure( state, doprint, 1 ); - if ( lua_pcall( state, 1, 0, 0 ) ) - { - fprintf( f, "[[ERROR IN CRASH HANDLER: %s]]", lua_tostring( state, -1 ) ); - } - fprintf( f, "\n" ); +void print_handler(lua_State* state) { + if (!luahandler || !f.is_open()) return; + f << "\nLua Crash Handler:\n\n"; + lua_rawgeti(state, LUA_REGISTRYINDEX, luahandler); + lua_pushlightuserdata(state, (void*) &f); + lua_pushcclosure(state, doprint, 1); + if (lua_pcall(state, 1, 0, 0)) + f << "[[ERROR IN CRASH HANDLER: " << lua_tostring(state, -1) << "]]"; + f << "\n"; + f.flush(); } -void print_traceback( FILE* f, lua_State* state ) -{ - fprintf( f, "\nMain Lua stack:\n" ); - int n = -1; - lua_Debug sar; - while ( lua_getstack( state, ++n, &sar ) ) { - lua_getinfo( state, "Sln", &sar ); - if ( *( sar.what ) == 'C' ) { - fprintf( f, "#%d\t%s in %s()\n", n, sar.short_src, sar.name ); - } else { - fprintf( f, "#%d\t%s:%d in %s %s() <%d-%d>\n", n, sar.short_src, sar.currentline, *( sar.namewhat ) ? sar.namewhat : "anonymous", - sar.name ? sar.name : "function", sar.linedefined, sar.lastlinedefined ); - } - } +void print_traceback(lua_State* state) { + if (!f.is_open()) return; + f << "\nMain Lua stack:\n"; + lua_Debug sar; + int n = -1; + while (lua_getstack(state, ++n, &sar)) { + lua_getinfo(state, "Sln", &sar); + if (*(sar.what) == 'C') + f << "#" << n << "\t" << sar.short_src << " in " << sar.name << "()\n"; + else + f << "#" << n << "\t" << sar.short_src << ":" << sar.currentline << " in " << (sar.namewhat && *(sar.namewhat) ? sar.namewhat : "anonymous") << " " + << (sar.name ? sar.name : "function") << " <" << sar.linedefined << "-" << sar.lastlinedefined << ">\n"; + } + f.flush(); } -int dumpstate( lua_State* state ) { - char buffer[64]; - time_t t = time(NULL); - struct tm& now = *localtime( &t ); - sprintf( buffer, "garrysmod/gcrash/luadump-%04d%02d%02d_%02d%02d%02d.txt", - now.tm_year + 1900, - now.tm_mon + 1, - now.tm_mday, - now.tm_hour, - now.tm_min, - now.tm_sec ); - FILE* f = fopen( buffer, "w" ); - if ( f ) { - fprintf( f, "Seg fault occured.\n" ); - print_traceback( f, state ); - print_handler( f, state ); - fclose( f ); - } - return 0; +int dumpstate(lua_State* state, const char* message = "** Segmentation fault occurred **\n") { + char buffer[64]; + time_t t = time(NULL); + struct tm now = *localtime(&t); + snprintf(buffer, sizeof(buffer), "garrysmod/gcrash/luadump-%04d%02d%02d_%02d%02d%02d.txt", + now.tm_year + 1900, now.tm_mon + 1, now.tm_mday, + now.tm_hour, now.tm_min, now.tm_sec); + + f.open(buffer, std::ios::out); + if (f.is_open()) { + f << message << "\n"; + print_traceback(state); + print_handler(state); + f.close(); + } + return 0; } -void handlesigsegv( int signum, siginfo_t* info, void* context ) -{ - dumpstate( L ); - abort(); +int dumpstate_caller(lua_State* state) { + printf("GCrash >> Dump stack called\n"); + dumpstate(state, "** Dump stack call **\n"); + return 0; } -int crash( lua_State* state ) -{ - *( (int*) NULL ) = 0; - return 0; +void handlesigsegv(int signum, siginfo_t* info, void* context) { + if (!frozen) { + printf("GCrash >> Segmentation fault detected\n"); + dumpstate(L, "** Segmentation fault occurred **\n"); + } + std::abort(); } -int sethandler( lua_State* state ) -{ - if ( luahandler ) luaL_unref( state, LUA_REGISTRYINDEX, luahandler ); - if ( lua_isfunction( state, 1 ) ) - { - lua_pushvalue( state, 1 ); - luahandler = luaL_ref( state, LUA_REGISTRYINDEX ); - } - return 0; +int crash(lua_State* state) { *((int*) NULL) = 0; return 0; } + +int sethandler(lua_State* state) { + if (luahandler) luaL_unref(state, LUA_REGISTRYINDEX, luahandler); + if (lua_isfunction(state, 1)) { + lua_pushvalue(state, 1); + luahandler = luaL_ref(state, LUA_REGISTRYINDEX); + } + return 0; } struct watchdog_update { - std::mutex mtx; - std::timed_mutex shutdown; - std::chrono::system_clock::time_point last_call; - std::chrono::system_clock::duration period; - bool sleeping; - lua_State* L; + std::mutex mtx; + std::timed_mutex shutdown; + std::chrono::system_clock::time_point last_call; + std::chrono::system_clock::duration period; + bool sleeping; }; -int watchdog_updatefn( lua_State* state ) -{ - watchdog_update* upd = ( watchdog_update* )lua_topointer( state, lua_upvalueindex( 1 ) ); - std::lock_guard lck( upd->mtx ); - upd->last_call = std::chrono::system_clock::now(); - return 0; -} - -void watchdog_hookfn( lua_State* state, lua_Debug* ar ) -{ - dumpstate( L ); - abort(); -} +watchdog_update* upd = nullptr; -void watchdog_threadfn( watchdog_update* upd ) -{ - std::unique_lock lck( upd->mtx ); - bool panicked = false; - while ( true ) { - auto dowait = upd->last_call + upd->period - std::chrono::system_clock::now(); - lck.unlock(); - if ( upd->shutdown.try_lock_for( dowait ) ) { - upd->shutdown.unlock(); - delete upd; - return; - } - lck.lock(); - if ( upd->sleeping ) { - upd->last_call = std::chrono::system_clock::now(); - } else if ( upd->last_call + upd->period < std::chrono::system_clock::now() ) { - if ( panicked ) break; - lua_sethook( upd->L, watchdog_hookfn, /* LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE */ 7, 0 ); - upd->last_call = std::chrono::system_clock::now(); - panicked = true; - } - } - dumpstate( upd->L ); - delete upd; - abort(); +int watchdogupdate(lua_State* state) { + std::lock_guard lck(upd->mtx); + upd->last_call = std::chrono::system_clock::now(); + return 0; } -int watchdog_ref; -int startwatchdog( lua_State* state ) -{ - if ( watchdog_ref ) - { - lua_rawgeti( state, LUA_REGISTRYINDEX, watchdog_ref ); - watchdog_update* upd = ( watchdog_update* ) lua_topointer( state, -1 ); - std::lock_guard lck( upd->mtx ); - upd->sleeping = false; - lua_pop( state, 1 ); - return 0; - } - - int time = lua_tointeger( state, 1 ); - if ( time < 10 ) - time = 30; - - watchdog_update* upd = new watchdog_update{}; - upd->last_call = std::chrono::system_clock::now(); - upd->L = state; - upd->period = std::chrono::seconds( time ); - upd->sleeping = false; - upd->shutdown.lock(); - - lua_pushlightuserdata( state, ( void* )upd ); - watchdog_ref = luaL_ref( state, LUA_REGISTRYINDEX ); - - lua_getglobal( state, "timer" ); - lua_getfield( state, -1, "Create" ); - lua_pushstring( state, "gcrash.watchdog" ); - lua_pushinteger( state, time / 3 ); - lua_pushinteger( state, 0 ); - lua_pushlightuserdata( state, ( void* )upd ); - lua_pushcclosure( state, watchdog_updatefn, 1 ); - lua_call( state, 4, 0 ); - lua_pop( state, 1 ); - - std::thread watchdog{ watchdog_threadfn, upd }; - watchdog.detach(); - return 0; +void watchdog_threadfn() { + std::unique_lock lck(upd->mtx); + while (true) { + auto dowait = upd->last_call + upd->period - std::chrono::system_clock::now(); + lck.unlock(); + if (upd->shutdown.try_lock_for(dowait)) { + upd->shutdown.unlock(); + return; + } + lck.lock(); + if (upd->sleeping) { + upd->last_call = std::chrono::system_clock::now(); + } else if (upd->last_call + upd->period < std::chrono::system_clock::now()) { + printf("GCrash >> Server freeze / hang / infinite loop detected\n"); + upd->last_call = std::chrono::system_clock::now(); + frozen = 1; + dumpstate(L, "** Server freeze / hang / infinite loop **"); + std::abort(); + } + } } -int stopwatchdog( lua_State* state ) -{ - if ( watchdog_ref ) - { - lua_rawgeti( state, LUA_REGISTRYINDEX, watchdog_ref ); - watchdog_update* upd = ( watchdog_update* ) lua_topointer( state, -1 ); - std::lock_guard lck( upd->mtx ); - upd->sleeping = true; - lua_pop( state, 1 ); - } - return 0; +int watchdog_ref = 0; + +int startwatchdog(lua_State* state) { + printf("GCrash >> Watchdog started\n"); + if (watchdog_ref) { + lua_rawgeti(state, LUA_REGISTRYINDEX, watchdog_ref); + std::lock_guard lck(upd->mtx); + upd->sleeping = false; + lua_pop(state, 1); + return 0; + } + + int time = lua_tointeger(state, 1); + if (time < 10) time = 30; + + upd->period = std::chrono::seconds(time); + upd->last_call = std::chrono::system_clock::now(); + upd->sleeping = false; + upd->shutdown.lock(); + + watchdog_ref = luaL_ref(state, LUA_REGISTRYINDEX); + + lua_getglobal(state, "timer"); + lua_getfield(state, -1, "Create"); + lua_pushstring(state, "gcrash.watchdog"); + lua_pushinteger(state, time / 3); + lua_pushinteger(state, 0); + lua_pushcclosure(state, watchdogupdate, 0); + lua_call(state, 4, 0); + lua_pop(state, 1); + + // Start the watchdog thread + std::thread watchdog{ watchdog_threadfn }; + watchdog.detach(); + return 0; } -int destroywatchdog( lua_State* state ) -{ - if ( watchdog_ref ) - { - lua_rawgeti( state, LUA_REGISTRYINDEX, watchdog_ref ); - luaL_unref( state, LUA_REGISTRYINDEX, watchdog_ref ); - watchdog_update* upd = ( watchdog_update* ) lua_topointer( state, -1 ); - std::lock_guard lck( upd->mtx ); - upd->shutdown.unlock(); - watchdog_ref = 0; - lua_pop( state, 1 ); - } - return 0; +int stopwatchdog(lua_State* state) { + printf("GCrash >> Watchdog stopped\n"); + if (watchdog_ref) { + lua_rawgeti(state, LUA_REGISTRYINDEX, watchdog_ref); + std::lock_guard lck(upd->mtx); + upd->sleeping = true; + lua_pop(state, 1); + } + return 0; } -DLL_EXPORT int gmod13_open( lua_State* state ) -{ - mkdir( "garrysmod/gcrash", 0755 ); - - L = state; - luahandler = 0; - watchdog_ref = 0; - - lua_newtable( state ); - { - luaD_setcfunction( state, "dumpstate", dumpstate ); - luaD_setcfunction( state, "sethandler", sethandler ); - luaD_setcfunction( state, "startwatchdog", startwatchdog ); - luaD_setcfunction( state, "stopwatchdog", stopwatchdog ); - luaD_setcfunction( state, "destroywatchdog", destroywatchdog ); - luaD_setcfunction( state, "crash", crash ); - } - lua_setglobal( state, "gcrash" ); - - struct sigaction action; - action.sa_sigaction = &handlesigsegv; - action.sa_flags = SA_SIGINFO; - sigaction( SIGSEGV, &action, NULL ); - - return 0; +int destroywatchdog(lua_State* state) { + printf("GCrash >> Watchdog destroyed\n"); + if (watchdog_ref) { + lua_rawgeti(state, LUA_REGISTRYINDEX, watchdog_ref); + luaL_unref(state, LUA_REGISTRYINDEX, watchdog_ref); + std::lock_guard lck(upd->mtx); + upd->shutdown.unlock(); + watchdog_ref = 0; + lua_pop(state, 1); + } + return 0; } -DLL_EXPORT int gmod13_close( lua_State* state ) -{ - return destroywatchdog( state ); +extern "C" { + DLL_EXPORT int gmod13_open(lua_State* state) { + printf("\n-------------------------\n>> GCrash v0.4.2 - VRP <<\n-------------------------\n"); + mkdir("garrysmod/gcrash", 0755); + + L = state; + luahandler = 0; + watchdog_ref = 0; + + upd = new watchdog_update{}; + upd->last_call = std::chrono::system_clock::now(); + upd->sleeping = false; + + lua_newtable(state); + luaD_setcfunction(state, "dumpstate", dumpstate_caller); + luaD_setcfunction(state, "sethandler", sethandler); + luaD_setcfunction(state, "startwatchdog", startwatchdog); + luaD_setcfunction(state, "stopwatchdog", stopwatchdog); + luaD_setcfunction(state, "destroywatchdog", destroywatchdog); + luaD_setcfunction(state, "crash", crash); + lua_setglobal(state, "gcrash"); + + struct sigaction action; + action.sa_sigaction = &handlesigsegv; + action.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &action, NULL); + + return 0; + } + + DLL_EXPORT int gmod13_close(lua_State* state) { + return destroywatchdog(state); + } } - diff --git a/src/main.cpp.x64 b/src/main.cpp.x64 new file mode 100644 index 0000000..9854e05 --- /dev/null +++ b/src/main.cpp.x64 @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include "lua_headers.h" + +// Globals +lua_State* L = nullptr; + +struct watchdog_update { + std::mutex mtx; + std::timed_mutex shutdown; + std::chrono::system_clock::time_point last_call; + std::chrono::system_clock::duration period; + bool sleeping; +}; + +watchdog_update* upd = nullptr; + +int watchdogupdate(lua_State* state) { + std::lock_guard lck(upd->mtx); + upd->last_call = std::chrono::system_clock::now(); + return 0; +} + +void watchdog_threadfn() { + std::unique_lock lck(upd->mtx); + while (true) { + auto dowait = upd->last_call + upd->period - std::chrono::system_clock::now(); + lck.unlock(); + if (upd->shutdown.try_lock_for(dowait)) { + upd->shutdown.unlock(); + return; + } + lck.lock(); + if (upd->sleeping) { + upd->last_call = std::chrono::system_clock::now(); + } else if (upd->last_call + upd->period < std::chrono::system_clock::now()) { + printf("GCrash >> Server freeze / hang / infinite loop detected\n"); + *((int*) NULL) = 0; // This will cause a segmentation fault, effectively crashing the game + } + } +} + +int watchdog_ref = 0; + +int startwatchdog(lua_State* state) { + printf("GCrash >> Watchdog started\n"); + if (watchdog_ref) { + lua_rawgeti(state, LUA_REGISTRYINDEX, watchdog_ref); + std::lock_guard lck(upd->mtx); + upd->sleeping = false; + lua_pop(state, 1); + return 0; + } + + int time = lua_tointeger(state, 1); + if (time < 10) time = 30; + + upd->period = std::chrono::seconds(time); + upd->last_call = std::chrono::system_clock::now(); + upd->sleeping = false; + upd->shutdown.lock(); + + watchdog_ref = luaL_ref(state, LUA_REGISTRYINDEX); + + lua_getglobal(state, "timer"); + lua_getfield(state, -1, "Create"); + lua_pushstring(state, "gcrash.watchdog"); + lua_pushinteger(state, time / 3); + lua_pushinteger(state, 0); + lua_pushcclosure(state, watchdogupdate, 0); + lua_call(state, 4, 0); + lua_pop(state, 1); + + // Start the watchdog thread + std::thread watchdog{ watchdog_threadfn }; + watchdog.detach(); + return 0; +} + +int stopwatchdog(lua_State* state) { + printf("GCrash >> Watchdog stopped\n"); + if (watchdog_ref) { + lua_rawgeti(state, LUA_REGISTRYINDEX, watchdog_ref); + std::lock_guard lck(upd->mtx); + upd->sleeping = true; + lua_pop(state, 1); + } + return 0; +} + +int destroywatchdog(lua_State* state) { + printf("GCrash >> Watchdog destroyed\n"); + if (watchdog_ref) { + lua_rawgeti(state, LUA_REGISTRYINDEX, watchdog_ref); + luaL_unref(state, LUA_REGISTRYINDEX, watchdog_ref); + std::lock_guard lck(upd->mtx); + upd->shutdown.unlock(); + watchdog_ref = 0; + lua_pop(state, 1); + } + return 0; +} + +// Fake crash function for testing or if u hate ur server +int crash(lua_State* state) { + printf("GCrash >> Fake crash initiated\n"); + *((int*) NULL) = 0; + return 0; +} + +extern "C" { + DLL_EXPORT int gmod13_open(lua_State* state) { + printf("\n-------------------------\n>> GCrash v0.4.3 - VRP <<\n-------------------------\n"); + + L = state; + watchdog_ref = 0; + + upd = new watchdog_update{}; + upd->last_call = std::chrono::system_clock::now(); + upd->sleeping = false; + + lua_newtable(state); + luaD_setcfunction(state, "startwatchdog", startwatchdog); + luaD_setcfunction(state, "stopwatchdog", stopwatchdog); + luaD_setcfunction(state, "destroywatchdog", destroywatchdog); + luaD_setcfunction(state, "crash", crash); + lua_setglobal(state, "gcrash"); + + return 0; + } + + DLL_EXPORT int gmod13_close(lua_State* state) { + return destroywatchdog(state); + } +}