diff --git a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java
index f01cb504..5a0d1bd7 100644
--- a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java
+++ b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java
@@ -17,6 +17,6 @@ public final class LuaJNI {
 	}
 
 	static {
-		//System.load(new File("lua_glue.dll").getAbsolutePath());
+		System.load(new File("lua_glue.dll").getAbsolutePath());
 	}
 }
diff --git a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java
index b15df21d..063deeed 100644
--- a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java
+++ b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java
@@ -13,7 +13,7 @@ import javax.annotation.Nullable;
 public interface LuaJNR {
 	public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, @LongLong long ctx, @LongLong long callback);
 	public int lua_callk(@NotNull Pointer luaState, int numArgs, int numResults, @LongLong long ctx, @LongLong long callback);
-	public long lua_atpanic(@NotNull Pointer luaState, long fn);
+	public long lua_atpanic(@NotNull Pointer luaState, @LongLong long fn);
 
 	@Nullable
 	public Pointer luaL_newstate();
@@ -75,7 +75,7 @@ public interface LuaJNR {
 	public void lua_pushstring(@NotNull Pointer luaState, @NotNull String value);
 
 	// двоичная строка
-	public long lua_pushlstring(@NotNull Pointer luaState, @NotNull Pointer stringPointer, @LongLong long length);
+	public long lua_pushlstring(@NotNull Pointer luaState, @LongLong long stringPointer, @LongLong long length);
 
 	// Загрузка Lua значений на стек
 	public int lua_getglobal(@NotNull Pointer luaState, @NotNull String name);
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt
index 720f6f48..d81b8108 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt
@@ -31,37 +31,18 @@ fun main() {
 	if (true) {
 		val lua = LuaState()
 
-		Thread.sleep(5_000L)
+		Thread.sleep(4_000L)
 
 		lua.load(File("test.lua").readText())
-		//lua.load("print('hello world!', ...)")
-
-		//lua.push(GsonBuilder().create().fromJson(File("playerdata.json").reader(), JsonElement::class.java))
-
-		/*lua.push(JsonObject().also {
-			it.add("Сыр", JsonPrimitive("Гиршок"))
-			it.add("сас", JsonPrimitive("сос"))
-			it.add("сыс", JsonPrimitive(4))
-			it.add("ы", JsonNull.INSTANCE)
-			it.add("s", JsonObject().also {
-				it.add("Вложенный", JsonPrimitive("Объект!"))
-			})
-		})*/
-
-		lua.pcall()
+		lua.call()
 
 		lua.loadGlobal("printTable")
 		lua.push(GsonBuilder().create().fromJson(File("playerdata.json").reader(), JsonElement::class.java))
-		lua.pcall(1)
+		lua.call(1)
 
-		/*for (t in 1 .. 100) {
-			lua.loadGlobal("test")
-
-			for (i in 0 until t)
-				lua.push("sass".repeat(t))
-
-			lua.pcall(t)
-		}*/
+		lua.loadGlobal("test")
+		lua.push("s".repeat(10))
+		lua.call(1)
 
 		Thread.sleep(4_000L)
 
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt
index 08517f39..fa767ea1 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt
@@ -9,18 +9,19 @@ import com.kenai.jffi.CallContext
 import com.kenai.jffi.CallingConvention
 import com.kenai.jffi.Closure
 import com.kenai.jffi.ClosureManager
+import com.kenai.jffi.MemoryIO
 import com.kenai.jffi.Type
-import com.sun.jna.Native
-import com.sun.jna.ptr.LongByReference
 import jnr.ffi.Memory
 import jnr.ffi.NativeType
-import jnr.ffi.Pointer
+import org.apache.logging.log4j.LogManager
 import org.lwjgl.system.MemoryUtil
 import java.io.Closeable
 import java.io.PrintWriter
 import java.io.StringWriter
+import java.lang.ref.Cleaner
 import java.nio.ByteBuffer
 import java.nio.ByteOrder
+import kotlin.system.exitProcess
 
 private fun stringToBuffer(str: String): ByteBuffer {
 	val bytes = str.toByteArray(charset = Charsets.UTF_8)
@@ -34,17 +35,34 @@ private fun stringToBuffer(str: String): ByteBuffer {
 
 class LuaState : Closeable {
 	private val pointer = LuaJNR.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState")
+	private val cleanable: Cleaner.Cleanable
+	private val sharedStringBufferPtr = MemoryIO.getInstance().allocateMemory(2L shl 16, false)
 
-	private var destroyed = false
-	private val panicHandler = object : LuaJNR.lua_CFunction {
-		override fun invoke(luaState: Pointer): Int {
-			println("$this panicked!")
-			return 0
+	init {
+		val pointer = pointer
+		val sharedStringBufferPtr = sharedStringBufferPtr
+
+		cleanable = CLEANER.register(this) {
+			LuaJNR.INSTANCE.lua_close(pointer)
+			MemoryIO.getInstance().freeMemory(sharedStringBufferPtr)
 		}
 	}
 
+	private val panicHandler = ClosureManager.getInstance().newClosure(
+		{
+			LOGGER.fatal("${this@LuaState} at $pointer has panicked! This should be impossible!")
+			exitProcess(1)
+		},
+
+		CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false)
+	)
+
+	override fun close() {
+		cleanable.clean()
+	}
+
 	init {
-		//LuaJNR.INSTANCE.lua_atpanic(pointer, panicHandler)
+		LuaJNR.INSTANCE.lua_atpanic(pointer, panicHandler.address)
 
 		LuaJNR.INSTANCE.luaopen_base(pointer)
 		storeGlobal("_G")
@@ -64,26 +82,15 @@ class LuaState : Closeable {
 		storeGlobal("debug")
 	}
 
-	fun pushClosure(lambda: (state: LuaState) -> Unit) {
-		LuaJNI.lua_pushcclosure(pointer.address()) lazy@{
-			try {
-				lambda.invoke(this@LuaState)
-			} catch (err: Throwable) {
-				val builder = StringWriter()
-				val printWriter = PrintWriter(builder)
-				err.printStackTrace(printWriter)
-				push(builder.toString())
-				return@lazy 1
-			}
-
-			return@lazy 0
-		}
-	}
-
-	fun checkStack(minAmount: Int): Boolean {
-		return LuaJNR.INSTANCE.lua_checkstack(pointer, minAmount) > 0
+	val stackTop: Int get() {
+		val value = LuaJNR.INSTANCE.lua_gettop(pointer)
+		check(value >= 0) { "Invalid stack top $value" }
+		return value
 	}
 
+	/**
+	 * Converts the acceptable index idx into an equivalent absolute index (that is, one that does not depend on the stack size).
+	 */
 	fun absoluteIndex(index: Int): Int {
 		return LuaJNR.INSTANCE.lua_absindex(pointer, index)
 	}
@@ -116,17 +123,17 @@ class LuaState : Closeable {
 		closure.dispose()
 	}
 
-	fun pcall(numArgs: Int = 0, numResults: Int = 0): Int {
+	fun call(numArgs: Int = 0, numResults: Int = 0): Int {
 		val status = LuaJNR.INSTANCE.lua_pcallk(pointer, numArgs, numResults, 0, 0L, 0L)
 
 		if (status == LUA_ERRRUN) {
-			throw LuaRuntimeException(getString())
+			throw LuaRuntimeException(popString())
 		}
 
 		return status
 	}
 
-	fun getString(index: Int = -1, limit: Long = 4096): String? {
+	fun popString(index: Int = -1, limit: Long = 2 shl 16): String? {
 		val len = Memory.allocateDirect(LuaJNR.RUNTIME, NativeType.SLONGLONG)
 		val p = LuaJNR.INSTANCE.lua_tolstring(pointer, absoluteIndex(index), len) ?: return null
 
@@ -143,21 +150,6 @@ class LuaState : Closeable {
 		return readBytes.toString(charset = Charsets.UTF_8)
 	}
 
-	override fun close() {
-		if (destroyed) {
-			throw IllegalStateException("Already destroyed")
-		}
-
-		LuaJNR.INSTANCE.lua_close(pointer)
-		destroyed = true
-	}
-
-	val stackTop: Int get() {
-		val value = LuaJNR.INSTANCE.lua_gettop(pointer)
-		check(value >= 0) { "Invalid stack top $value" }
-		return value
-	}
-
 	fun storeGlobal(name: String) {
 		LuaJNR.INSTANCE.lua_setglobal(pointer, name)
 	}
@@ -166,6 +158,22 @@ class LuaState : Closeable {
 		LuaJNR.INSTANCE.lua_getglobal(pointer, name)
 	}
 
+	fun push(closure: (state: LuaState) -> Unit) {
+		LuaJNI.lua_pushcclosure(pointer.address()) lazy@{
+			try {
+				closure.invoke(this@LuaState)
+			} catch (err: Throwable) {
+				val builder = StringWriter()
+				val printWriter = PrintWriter(builder)
+				err.printStackTrace(printWriter)
+				push(builder.toString())
+				return@lazy 1
+			}
+
+			return@lazy 0
+		}
+	}
+
 	fun push() {
 		LuaJNR.INSTANCE.lua_pushnil(pointer)
 	}
@@ -178,23 +186,53 @@ class LuaState : Closeable {
 		LuaJNR.INSTANCE.lua_pushinteger(pointer, value)
 	}
 
+	fun push(value: Double) {
+		LuaJNR.INSTANCE.lua_pushnumber(pointer, value)
+	}
+
+	fun push(value: Float) {
+		LuaJNR.INSTANCE.lua_pushnumber(pointer, value.toDouble())
+	}
+
+	fun push(value: Boolean) {
+		LuaJNR.INSTANCE.lua_pushboolean(pointer, if (value) 1 else 0)
+	}
+
 	fun push(value: String) {
 		val bytes = value.toByteArray(Charsets.UTF_8)
 
-		val block = LuaJNR.RUNTIME.memoryManager.allocateDirect(bytes.size) ?: throw OutOfMemoryError("Unable to allocate ${bytes.size} bytes on heap")
+		if (bytes.size < 2 shl 16) {
+			MemoryIO.getInstance().putByteArray(sharedStringBufferPtr, bytes, 0, bytes.size)
+			LuaJNR.INSTANCE.lua_pushlstring(pointer, sharedStringBufferPtr, bytes.size.toLong())
+		} else {
+			val mem = MemoryIO.getInstance()
+			val block = mem.allocateMemory(bytes.size.toLong(), false)
 
-		//try {
-			block.put(0L, bytes, 0, bytes.size)
-			LuaJNR.INSTANCE.lua_pushlstring(pointer, block, bytes.size.toLong())
-		//} finally {
-		//	Memory.allocate()
-		//}
+			if (block == 0L)
+				throw OutOfMemoryError("Unable to allocate ${bytes.size} bytes on heap")
+
+			try {
+				mem.putByteArray(block, bytes, 0, bytes.size)
+				LuaJNR.INSTANCE.lua_pushlstring(pointer, block, bytes.size.toLong())
+			} finally {
+				mem.freeMemory(block)
+			}
+		}
+	}
+
+	fun pushTable(arraySize: Int = 0, hashSize: Int = 0): Int {
+		LuaJNR.INSTANCE.lua_createtable(pointer, arraySize, hashSize)
+		return stackTop
+	}
+
+	fun setTableValue(stackIndex: Int) {
+		LuaJNR.INSTANCE.lua_settable(pointer, stackIndex)
 	}
 
 	fun push(value: JsonElement) {
 		when (value) {
 			JsonNull.INSTANCE -> {
-				LuaJNR.INSTANCE.lua_pushnil(pointer)
+				push()
 			}
 
 			is JsonPrimitive -> {
@@ -202,39 +240,37 @@ class LuaState : Closeable {
 					val num = value.asNumber
 
 					when (num) {
-						is Int, is Long -> LuaJNR.INSTANCE.lua_pushinteger(pointer, num.toLong())
-						else -> LuaJNR.INSTANCE.lua_pushnumber(pointer, num.toDouble())
+						is Int, is Long -> push(num.toLong())
+						else -> push(num.toDouble())
 					}
 				} else if (value.isString) {
 					push(value.asString)
 				} else if (value.isBoolean) {
-					LuaJNR.INSTANCE.lua_pushboolean(pointer, if (value.asBoolean) 1 else 0)
+					push(value.asBoolean)
 				} else {
 					throw IllegalArgumentException(value.toString())
 				}
 			}
 
 			is JsonArray -> {
-				LuaJNR.INSTANCE.lua_createtable(pointer, value.size(), 0)
-				val index = stackTop
+				val index = pushTable(arraySize = value.size())
 
 				for ((i, v) in value.withIndex()) {
-					LuaJNR.INSTANCE.lua_pushinteger(pointer, i.toLong() + 1L)
+					push(i + 1L)
 					push(v)
 
-					LuaJNR.INSTANCE.lua_settable(pointer, index)
+					setTableValue(index)
 				}
 			}
 
 			is JsonObject -> {
-				LuaJNR.INSTANCE.lua_createtable(pointer, 0, value.size())
-				val index = stackTop
+				val index = pushTable(hashSize = value.size())
 
 				for ((k, v) in value.entrySet()) {
 					push(k)
 					push(v)
 
-					LuaJNR.INSTANCE.lua_settable(pointer, index)
+					setTableValue(index)
 				}
 			}
 
@@ -243,4 +279,13 @@ class LuaState : Closeable {
 			}
 		}
 	}
+
+	companion object {
+		private val LOGGER = LogManager.getLogger()
+		private val CLEANER = Cleaner.create {
+			val thread = Thread(it, "LuaState cleaner")
+			thread.priority = 1
+			thread
+		}
+	}
 }
diff --git a/test.lua b/test.lua
index 3312a8b1..b93ee9aa 100644
--- a/test.lua
+++ b/test.lua
@@ -16,7 +16,7 @@ function printTable(input)
 end
 
 function test(...)
-    print('Called test with ' .. select('#', ...) .. ' arguments: ' .. table.concat({...}, ', '))
+    print('Called test with ' .. select('#', ...) .. ' arguments: ' .. #table.concat({...}, ', '))
 end
 
 print(collectgarbage('count') * 1024)