diff --git a/LUA_API.rst b/LUA_API.rst
index 2859f8678..f3d26eaca 100644
--- a/LUA_API.rst
+++ b/LUA_API.rst
@@ -1176,6 +1176,26 @@ and are only documented here for completeness:
Returns a sequence of tables describing virtual memory ranges of the process.
+* ``dfhack.internal.memmove(dest,src,count)``
+
+ Wraps the standard memmove function. Accepts both numbers and refs as pointers.
+
+* ``dfhack.internal.memcmp(ptr1,ptr2,count)``
+
+ Wraps the standard memcmp function.
+
+* ``dfhack.internal.memscan(haystack,count,step,needle,nsize)``
+
+ Searches for ``needle`` of ``nsize`` bytes in ``haystack``,
+ using ``count`` steps of ``step`` bytes.
+ Returns: *step_idx, sum_idx, found_ptr*, or *nil* if not found.
+
+* ``dfhack.internal.diffscan(old_data, new_data, start_idx, end_idx, eltsize[, oldval, newval, delta])``
+
+ Searches for differences between buffers at ptr1 and ptr2, as integers of size eltsize.
+ The oldval, newval or delta arguments may be used to specify additional constraints.
+ Returns: *found_index*, or *nil* if end reached.
+
Core interpreter context
========================
diff --git a/Lua API.html b/Lua API.html
index 047457985..90e612116 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -1332,6 +1332,22 @@ global environment, persistent between calls to the script.
dfhack.internal.getMemRanges()
Returns a sequence of tables describing virtual memory ranges of the process.
+dfhack.internal.memmove(dest,src,count)
+Wraps the standard memmove function. Accepts both numbers and refs as pointers.
+
+dfhack.internal.memcmp(ptr1,ptr2,count)
+Wraps the standard memcmp function.
+
+dfhack.internal.memscan(haystack,count,step,needle,nsize)
+Searches for needle of nsize bytes in haystack,
+using count steps of step bytes.
+Returns: step_idx, sum_idx, found_ptr, or nil if not found.
+
+dfhack.internal.diffscan(old_data, new_data, start_idx, end_idx, eltsize[, oldval, newval, delta])
+Searches for differences between buffers at ptr1 and ptr2, as integers of size eltsize.
+The oldval, newval or delta arguments may be used to specify additional constraints.
+Returns: found_index, or nil if end reached.
+
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index 59956a2c8..50458f119 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -1021,6 +1021,21 @@ static const luaL_Reg dfhack_constructions_funcs[] = {
/***** Internal module *****/
+static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
+{
+ luaL_checkany(L, idx);
+ void *rv;
+ if (lua_isnil(L, idx))
+ rv = NULL;
+ else if (lua_type(L, idx) == LUA_TNUMBER)
+ rv = (void*)lua_tointeger(L, idx);
+ else
+ rv = Lua::CheckDFObject(L, NULL, idx);
+ if (!rv && !allow_null)
+ luaL_argerror(L, idx, "null pointer");
+ return rv;
+}
+
static uint32_t getBase() { return Core::getInstance().p->getBase(); }
static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
@@ -1042,7 +1057,7 @@ static int internal_getAddress(lua_State *L)
static int internal_setAddress(lua_State *L)
{
std::string name = luaL_checkstring(L, 1);
- uint32_t addr = luaL_checkint(L, 2);
+ uint32_t addr = (uint32_t)checkaddr(L, 2, true);
internal_getAddress(L);
// Set the address
@@ -1097,10 +1112,99 @@ static int internal_getMemRanges(lua_State *L)
return 1;
}
+static int internal_memmove(lua_State *L)
+{
+ void *dest = checkaddr(L, 1);
+ void *src = checkaddr(L, 2);
+ int size = luaL_checkint(L, 3);
+ if (size < 0) luaL_argerror(L, 1, "negative size");
+ memmove(dest, src, size);
+ return 0;
+}
+
+static int internal_memcmp(lua_State *L)
+{
+ void *dest = checkaddr(L, 1);
+ void *src = checkaddr(L, 2);
+ int size = luaL_checkint(L, 3);
+ if (size < 0) luaL_argerror(L, 1, "negative size");
+ lua_pushinteger(L, memcmp(dest, src, size));
+ return 1;
+}
+
+static int internal_memscan(lua_State *L)
+{
+ uint8_t *haystack = (uint8_t*)checkaddr(L, 1);
+ int hcount = luaL_checkint(L, 2);
+ int hstep = luaL_checkint(L, 3);
+ if (hstep == 0) luaL_argerror(L, 3, "zero step");
+ void *needle = checkaddr(L, 4);
+ int nsize = luaL_checkint(L, 5);
+ if (nsize < 0) luaL_argerror(L, 5, "negative size");
+
+ for (int i = 0; i <= hcount; i++)
+ {
+ uint8_t *p = haystack + i*hstep;
+ if (memcmp(p, needle, nsize) == 0) {
+ lua_pushinteger(L, i);
+ lua_pushinteger(L, (lua_Integer)p);
+ return 2;
+ }
+ }
+
+ lua_pushnil(L);
+ return 1;
+}
+
+static int internal_diffscan(lua_State *L)
+{
+ lua_settop(L, 8);
+ void *old_data = checkaddr(L, 1);
+ void *new_data = checkaddr(L, 2);
+ int start_idx = luaL_checkint(L, 3);
+ int end_idx = luaL_checkint(L, 4);
+ int eltsize = luaL_checkint(L, 5);
+ bool has_oldv = !lua_isnil(L, 6);
+ bool has_newv = !lua_isnil(L, 7);
+ bool has_diffv = !lua_isnil(L, 8);
+
+#define LOOP(esz, etype) \
+ case esz: { \
+ etype *pold = (etype*)old_data; \
+ etype *pnew = (etype*)new_data; \
+ etype oldv = (etype)luaL_optint(L, 6, 0); \
+ etype newv = (etype)luaL_optint(L, 7, 0); \
+ etype diffv = (etype)luaL_optint(L, 8, 0); \
+ for (int i = start_idx; i < end_idx; i++) { \
+ if (pold[i] == pnew[i]) continue; \
+ if (has_oldv && pold[i] != oldv) continue; \
+ if (has_newv && pnew[i] != newv) continue; \
+ if (has_diffv && etype(pnew[i]-pold[i]) != diffv) continue; \
+ lua_pushinteger(L, i); return 1; \
+ } \
+ break; \
+ }
+ switch (eltsize) {
+ LOOP(1, uint8_t);
+ LOOP(2, uint16_t);
+ LOOP(4, uint32_t);
+ default:
+ luaL_argerror(L, 5, "invalid element size");
+ }
+#undef LOOP
+
+ lua_pushnil(L);
+ return 1;
+}
+
static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress },
{ "getMemRanges", internal_getMemRanges },
+ { "memmove", internal_memmove },
+ { "memcmp", internal_memcmp },
+ { "memscan", internal_memscan },
+ { "diffscan", internal_diffscan },
{ NULL, NULL }
};