diff --git a/Lua API.html b/Lua API.html
index 9c18b6585..be52ea985 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -1714,6 +1714,15 @@ global environment, persistent between calls to the script.
If destination overlaps a completely invalid memory region, or another error
occurs, returns false.
+dfhack.internal.patchBytes(write_table[, verify_table])
+The first argument must be a lua table, which is interpreted as a mapping from
+memory addresses to byte values that should be stored there. The second argument
+may be a similar table of values that need to be checked before writing anything.
+The function takes care to either apply all of write_table, or none of it.
+An empty write_table with a nonempty verify_table can be used to reasonably
+safely check if the memory contains certain values.
+Returns true if successful, or nil, error_msg, address if not.
+
dfhack.internal.memmove(dest,src,count)
Wraps the standard memmove function. Accepts both numbers and refs as pointers.
diff --git a/Lua API.rst b/Lua API.rst
index 185672816..8dad3663b 100644
--- a/Lua API.rst
+++ b/Lua API.rst
@@ -1581,6 +1581,18 @@ and are only documented here for completeness:
If destination overlaps a completely invalid memory region, or another error
occurs, returns false.
+* ``dfhack.internal.patchBytes(write_table[, verify_table])``
+
+ The first argument must be a lua table, which is interpreted as a mapping from
+ memory addresses to byte values that should be stored there. The second argument
+ may be a similar table of values that need to be checked before writing anything.
+
+ The function takes care to either apply all of ``write_table``, or none of it.
+ An empty ``write_table`` with a nonempty ``verify_table`` can be used to reasonably
+ safely check if the memory contains certain values.
+
+ Returns *true* if successful, or *nil, error_msg, address* if not.
+
* ``dfhack.internal.memmove(dest,src,count)``
Wraps the standard memmove function. Accepts both numbers and refs as pointers.
diff --git a/library/Core.cpp b/library/Core.cpp
index 9986c3396..111774f26 100644
--- a/library/Core.cpp
+++ b/library/Core.cpp
@@ -1657,14 +1657,11 @@ bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write)
if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared)
return false;
- if (!write)
- return true;
-
// Apply writable permissions & update
for (unsigned i = start; i < end; i++)
{
auto &perms = ranges[i];
- if (perms.write && perms.read)
+ if ((perms.write || !write) && perms.read)
continue;
save.push_back(perms);
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index 593ac3d8d..6ca1188d9 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -1535,6 +1535,81 @@ static int internal_patchMemory(lua_State *L)
return 1;
}
+static int internal_patchBytes(lua_State *L)
+{
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_settop(L, 2);
+
+ MemoryPatcher patcher;
+
+ if (!lua_isnil(L, 2))
+ {
+ luaL_checktype(L, 2, LUA_TTABLE);
+
+ lua_pushnil(L);
+
+ while (lua_next(L, 2))
+ {
+ uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
+ int isnum;
+ uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum);
+ if (!isnum)
+ luaL_error(L, "invalid value in verify table");
+ lua_pop(L, 1);
+
+ if (!patcher.verifyAccess(addr, 1, false))
+ {
+ lua_pushnil(L);
+ lua_pushstring(L, "invalid verify address");
+ lua_pushvalue(L, -3);
+ return 3;
+ }
+
+ if (*addr != value)
+ {
+ lua_pushnil(L);
+ lua_pushstring(L, "wrong verify value");
+ lua_pushvalue(L, -3);
+ return 3;
+ }
+ }
+ }
+
+ lua_pushnil(L);
+
+ while (lua_next(L, 1))
+ {
+ uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
+ int isnum;
+ uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum);
+ if (!isnum)
+ luaL_error(L, "invalid value in write table");
+ lua_pop(L, 1);
+
+ if (!patcher.verifyAccess(addr, 1, true))
+ {
+ lua_pushnil(L);
+ lua_pushstring(L, "invalid write address");
+ lua_pushvalue(L, -3);
+ return 3;
+ }
+ }
+
+ lua_pushnil(L);
+
+ while (lua_next(L, 1))
+ {
+ uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
+ uint8_t value = (uint8_t)lua_tounsigned(L, -1);
+ lua_pop(L, 1);
+
+ *addr = value;
+ }
+
+ lua_pushboolean(L, true);
+ return 1;
+}
+
static int internal_memmove(lua_State *L)
{
void *dest = checkaddr(L, 1);
@@ -1626,6 +1701,7 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "getVTable", internal_getVTable },
{ "getMemRanges", internal_getMemRanges },
{ "patchMemory", internal_patchMemory },
+ { "patchBytes", internal_patchBytes },
{ "memmove", internal_memmove },
{ "memcmp", internal_memcmp },
{ "memscan", internal_memscan },
diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp
index 6bf218ba0..c052b88aa 100644
--- a/library/LuaTools.cpp
+++ b/library/LuaTools.cpp
@@ -107,7 +107,8 @@ static void signal_typeid_error(color_ostream *out, lua_State *state,
type_identity *type, const char *msg,
int val_index, bool perr, bool signal)
{
- std::string error = stl_sprintf(msg, type->getFullName().c_str());
+ std::string typestr = type ? type->getFullName() : "any pointer";
+ std::string error = stl_sprintf(msg, typestr.c_str());
if (signal)
{
@@ -134,6 +135,8 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_
if (lua_isnil(state, val_index))
return NULL;
+ if (lua_islightuserdata(state, val_index) && !lua_touserdata(state, val_index))
+ return NULL;
void *rv = get_object_internal(state, type, val_index, exact_type, false);