diff --git a/CMakeLists.txt b/CMakeLists.txt index 1199f2897..60b4ab656 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ ENDIF() # use shared libraries for protobuf ADD_DEFINITIONS(-DPROTOBUF_USE_DLLS) +ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL) if(UNIX) add_definitions(-D_LINUX) diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index b135f221d..23ea6a485 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -1,6 +1,9 @@ PROJECT ( lua C ) CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +# TODO: make this RelWithDebInfo only +ADD_DEFINITIONS(-DLUA_USE_APICHECK) + IF(WIN32) ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE ) ELSE() @@ -76,8 +79,13 @@ src/lzio.c ) LIST(APPEND SRC_LIBLUA ${HDR_LIBLUA}) -ADD_LIBRARY ( lua STATIC EXCLUDE_FROM_ALL ${SRC_LIBLUA} ) +ADD_LIBRARY ( lua SHARED EXCLUDE_FROM_ALL ${SRC_LIBLUA} ) TARGET_LINK_LIBRARIES ( lua ${LIBS}) + +install(TARGETS lua + LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} + RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) + IDE_FOLDER(lua "Depends") #SET ( SRC_LUA src/lua.c ) diff --git a/depends/lua/include/luaconf.h b/depends/lua/include/luaconf.h index 660793356..b202967b3 100644 --- a/depends/lua/include/luaconf.h +++ b/depends/lua/include/luaconf.h @@ -151,11 +151,20 @@ ** the libraries, you may want to use the following definition (define ** LUA_BUILD_AS_DLL to get it). */ +#ifdef __cplusplus + #define LUA_API_EXTERN extern "C" +#else + #define LUA_API_EXTERN extern +#endif #if defined(LUA_BUILD_AS_DLL) - #if defined(LUA_CORE) || defined(LUA_LIB) - #define LUA_API __declspec(dllexport) + #if defined(_MSC_VER) + #if defined(LUA_CORE) || defined(LUA_LIB) + #define LUA_API __declspec(dllexport) LUA_API_EXTERN + #else + #define LUA_API __declspec(dllimport) LUA_API_EXTERN + #endif #else - #define LUA_API __declspec(dllimport) + #define LUA_API LUA_API_EXTERN __attribute__ ((visibility("default"))) #endif #else #ifdef __cplusplus diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 63107456a..72bf91e62 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -55,8 +55,10 @@ SET(MAIN_SOURCES Core.cpp ColorText.cpp DataDefs.cpp +LuaWrapper.cpp DataStatics.cpp DataStaticsCtor.cpp +DataStaticsFields.cpp MiscUtils.cpp PluginManager.cpp TileTypes.cpp @@ -195,6 +197,14 @@ ADD_CUSTOM_COMMAND( ADD_CUSTOM_TARGET(generate_headers DEPENDS ${dfapi_SOURCE_DIR}/include/df/static.inc) +IF(UNIX) + # Don't produce debug info for generated stubs + SET_SOURCE_FILES_PROPERTIES(DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp + PROPERTIES COMPILE_FLAGS "-g0 -O1") +ELSE(WIN32) +ENDIF() + + # Compilation ADD_DEFINITIONS(-DBUILD_DFHACK_LIB) @@ -242,7 +252,7 @@ ENDIF() #effectively disables debug builds... SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) -TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket ${PROJECT_LIBS}) +TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS}) SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "") TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 30a4da271..6fcd1141a 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -34,28 +34,186 @@ distribution. #include "tinythread.h" // must be last due to MS stupidity #include "DataDefs.h" +#include "DataIdentity.h" #include "MiscUtils.h" using namespace DFHack; + +void *type_identity::do_allocate_pod() { + void *p = malloc(size); + memset(p, 0, size); + return p; +} + +void type_identity::do_copy_pod(void *tgt, const void *src) { + memmove(tgt, src, size); +}; + +void *type_identity::allocate() { + if (can_allocate()) + return do_allocate(); + else + return NULL; +} + +bool type_identity::copy(void *tgt, const void *src) { + if (can_allocate() && tgt && src) + do_copy(tgt, src); + else + return false; +} + +void *enum_identity::do_allocate() { + void *p = malloc(byte_size()); + memcpy(p, &first_item_value, std::min(byte_size(), sizeof(int64_t))); + return p; +} + /* The order of global object constructor calls is * undefined between compilation units. Therefore, * this list has to be plain data, so that it gets * initialized by the loader in the initial mmap. */ -virtual_identity *virtual_identity::list = NULL; +compound_identity *compound_identity::list = NULL; +std::vector compound_identity::top_scope; -virtual_identity::virtual_identity(const char *dfhack_name, const char *original_name, virtual_identity *parent) - : dfhack_name(dfhack_name), original_name(original_name), parent(parent), - prev(NULL), vtable_ptr(NULL), has_children(true) +compound_identity::compound_identity(size_t size, TAllocateFn alloc, + compound_identity *scope_parent, const char *dfhack_name) + : constructed_identity(size, alloc), scope_parent(scope_parent), dfhack_name(dfhack_name) { - // Link into the static list. Nothing else can be safely done at this point. - next = list; list = this; + next = list; list = this; +} + +void compound_identity::doInit(Core *) +{ + if (scope_parent) + scope_parent->scope_children.push_back(this); + else + top_scope.push_back(this); +} + +std::string compound_identity::getFullName() +{ + if (scope_parent) + return scope_parent->getFullName() + "." + getName(); + else + return getName(); } -/* Vtable to identity lookup. */ static tthread::mutex *known_mutex = NULL; + +void compound_identity::Init(Core *core) +{ + if (!known_mutex) + known_mutex = new tthread::mutex(); + + // This cannot be done in the constructors, because + // they are called in an undefined order. + for (compound_identity *p = list; p; p = p->next) + p->doInit(core); + + //FIXME: ... nuked. the group was empty... +/* + // Read pre-filled vtable ptrs + OffsetGroup *ptr_table = core->vinfo->getGroup("vtable"); + for (virtual_identity *p = list; p; p = p->next) { + void * tmp; + if (ptr_table->getSafeAddress(p->getName(),tmp)) + p->vtable_ptr = tmp; + } + */ +} + +bitfield_identity::bitfield_identity(size_t size, + compound_identity *scope_parent, const char *dfhack_name, + int num_bits, const bitfield_item_info *bits) + : compound_identity(size, NULL, scope_parent, dfhack_name), bits(bits), num_bits(num_bits) +{ +} + +enum_identity::enum_identity(size_t size, + compound_identity *scope_parent, const char *dfhack_name, + type_identity *base_type, + int64_t first_item_value, int64_t last_item_value, + const char *const *keys) + : compound_identity(size, NULL, scope_parent, dfhack_name), + first_item_value(first_item_value), last_item_value(last_item_value), + keys(keys), base_type(base_type) +{ +} + +struct_identity::struct_identity(size_t size, TAllocateFn alloc, + compound_identity *scope_parent, const char *dfhack_name, + struct_identity *parent, const struct_field_info *fields) + : compound_identity(size, alloc, scope_parent, dfhack_name), + parent(parent), has_children(false), fields(fields) +{ +} + +void struct_identity::doInit(Core *core) +{ + compound_identity::doInit(core); + + if (parent) { + parent->children.push_back(this); + parent->has_children = true; + } +} + +bool struct_identity::is_subclass(struct_identity *actual) +{ + for (; actual; actual = actual->getParent()) + if (actual == this) return true; + + return false; +} + +std::string pointer_identity::getFullName() +{ + return (target ? target->getFullName() : std::string("void")) + "*"; +} + +std::string container_identity::getFullName(type_identity *item) +{ + return "<" + (item ? item->getFullName() : std::string("void")) + ">"; +} + +std::string ptr_container_identity::getFullName(type_identity *item) +{ + return "<" + (item ? item->getFullName() : std::string("void")) + "*>"; +} + +std::string bit_container_identity::getFullName(type_identity *) +{ + return ""; +} + +std::string df::buffer_container_identity::getFullName(type_identity *item) +{ + return (item ? item->getFullName() : std::string("void")) + + (size > 0 ? stl_sprintf("[%d]", size) : std::string("[]")); +} + +virtual_identity::virtual_identity(size_t size, TAllocateFn alloc, + const char *dfhack_name, const char *original_name, + virtual_identity *parent, const struct_field_info *fields) + : struct_identity(size, alloc, NULL, dfhack_name, parent, fields), original_name(original_name), + vtable_ptr(NULL) +{ +} + +static std::map name_lookup; + +void virtual_identity::doInit(Core *core) +{ + struct_identity::doInit(core); + + name_lookup[getOriginalName()] = this; +} + +/* Vtable to identity lookup. */ std::map virtual_identity::known; virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) @@ -78,8 +236,9 @@ virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) virtual_identity *actual = NULL; - for (virtual_identity *p = list; p; p = p->next) { - if (strcmp(name.c_str(), p->getOriginalName()) != 0) continue; + auto name_it = name_lookup.find(name); + if (name_it != name_lookup.end()) { + virtual_identity *p = name_it->second; if (p->vtable_ptr && p->vtable_ptr != vtable) { std::cerr << "Conflicting vtable ptr for class '" << p->getName() @@ -103,14 +262,6 @@ virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) return NULL; } -bool virtual_identity::is_subclass(virtual_identity *actual) -{ - for (; actual; actual = actual->parent) - if (actual == this) return true; - - return false; -} - void virtual_identity::adjust_vtable(virtual_ptr obj, virtual_identity *main) { if (vtable_ptr) { @@ -135,35 +286,6 @@ virtual_ptr virtual_identity::clone(virtual_ptr obj) return copy; } -void virtual_identity::Init(Core *core) -{ - if (!known_mutex) - known_mutex = new tthread::mutex(); - - // This cannot be done in the constructors, because - // they are called in an undefined order. - for (virtual_identity *p = list; p; p = p->next) { - p->has_children = false; - p->children.clear(); - } - for (virtual_identity *p = list; p; p = p->next) { - if (p->parent) { - p->parent->children.push_back(p); - p->parent->has_children = true; - } - } - //FIXME: ... nuked. the group was empty... -/* - // Read pre-filled vtable ptrs - OffsetGroup *ptr_table = core->vinfo->getGroup("vtable"); - for (virtual_identity *p = list; p; p = p->next) { - void * tmp; - if (ptr_table->getSafeAddress(p->getName(),tmp)) - p->vtable_ptr = tmp; - } - */ -} - bool DFHack::findBitfieldField(unsigned *idx, const std::string &name, unsigned size, const bitfield_item_info *items) { diff --git a/library/DataStatics.cpp b/library/DataStatics.cpp index 5ccabb7e1..1e4b21be9 100644 --- a/library/DataStatics.cpp +++ b/library/DataStatics.cpp @@ -7,6 +7,8 @@ #include "df/world_data.h" #include "df/ui.h" +#include "DataIdentity.h" + namespace { template inline T &_toref(T &r) { return r; } @@ -21,6 +23,8 @@ namespace { #define INIT_GLOBAL_FUNCTION_ITEM(type,name) \ if (global_table_->getAddress(#name,tmp_)) name = (type*)tmp_; +#define TID(type) (&identity_traits< type >::identity) + // Instantiate all the static objects #include "df/static.inc" #include "df/static.enums.inc" diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp new file mode 100644 index 000000000..89736369f --- /dev/null +++ b/library/DataStaticsFields.cpp @@ -0,0 +1,49 @@ +#include "Internal.h" +#include "DataDefs.h" +#include "MiscUtils.h" +#include "VersionInfo.h" + +#include "df/world.h" +#include "df/world_data.h" +#include "df/ui.h" + +#include "DataIdentity.h" + +#include + +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +namespace df { +#define NUMBER_IDENTITY_TRAITS(type) \ + number_identity identity_traits::identity(#type); + + NUMBER_IDENTITY_TRAITS(char); + NUMBER_IDENTITY_TRAITS(int8_t); + NUMBER_IDENTITY_TRAITS(uint8_t); + NUMBER_IDENTITY_TRAITS(int16_t); + NUMBER_IDENTITY_TRAITS(uint16_t); + NUMBER_IDENTITY_TRAITS(int32_t); + NUMBER_IDENTITY_TRAITS(uint32_t); + NUMBER_IDENTITY_TRAITS(int64_t); + NUMBER_IDENTITY_TRAITS(uint64_t); + NUMBER_IDENTITY_TRAITS(float); + + bool_identity identity_traits::identity; + stl_string_identity identity_traits::identity; + pointer_identity identity_traits::identity; + stl_ptr_vector_identity identity_traits >::identity; + stl_bit_vector_identity identity_traits >::identity; + + buffer_container_identity buffer_container_identity::base_instance; + +#undef NUMBER_IDENTITY_TRAITS +} + +#define TID(type) (&identity_traits< type >::identity) + +#define FLD(mode, name) struct_field_info::mode, #name, offsetof(CUR_STRUCT, name) +#define GFLD(mode, name) struct_field_info::mode, #name, (size_t)&df::global::name +#define FLD_END struct_field_info::END + +// Field definitions +#include "df/static.fields.inc" diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp new file mode 100644 index 000000000..d6edef4e2 --- /dev/null +++ b/library/LuaWrapper.cpp @@ -0,0 +1,1722 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Internal.h" + +#include +#include +#include + +#include "MemAccess.h" +#include "Core.h" +#include "VersionInfo.h" +#include "tinythread.h" +// must be last due to MS stupidity +#include "DataDefs.h" +#include "DataIdentity.h" + +#include "MiscUtils.h" + +#include +#include + +using namespace DFHack; + +static luaL_Reg no_functions[] = { { NULL, NULL } }; + +inline void lua_dup(lua_State *state) { lua_pushvalue(state, -1); } +inline void lua_swap(lua_State *state) { lua_insert(state, -2); } + +/* + * Registry name: hash of type metatables <-> type identities. + */ +#define DFHACK_TYPETABLE_NAME "DFHack::DFTypes" + +/* + * Registry name: hash of type identity -> node in df.etc... + */ +#define DFHACK_TYPEID_TABLE_NAME "DFHack::DFTypeIds" + +/* + * Registry name: hash of enum/bitfield identity -> index lookup table + */ +#define DFHACK_ENUM_TABLE_NAME "DFHack::DFEnums" + +/* + * Registry name: hash of pointer target identity <-> adhoc pointer identity userdata. + */ +#define DFHACK_PTR_IDTABLE_NAME "DFHack::PtrDFTypes" + +// Function registry names +#define DFHACK_CHANGEERROR_NAME "DFHack::ChangeError" +#define DFHACK_COMPARE_NAME "DFHack::ComparePtrs" +#define DFHACK_TYPE_TOSTRING_NAME "DFHack::TypeToString" +#define DFHACK_SIZEOF_NAME "DFHack::Sizeof" +#define DFHACK_DISPLACE_NAME "DFHack::Displace" + +/* + * Upvalue: contents of DFHACK_TYPETABLE_NAME + */ +#define UPVAL_TYPETABLE lua_upvalueindex(1) + +/* + * Expected metatable of the current object. + */ +#define UPVAL_METATABLE lua_upvalueindex(2) + +/* + * Table mapping field names to indices or data structure pointers. + * Enum index table is linked into here via getmetatable($).__index. + * Fields that are actually in UPVAL_METATABLE are marked with NULL light udata. + */ +#define UPVAL_FIELDTABLE lua_upvalueindex(3) + +/* + * Only for containers: light udata with container identity. + */ +#define UPVAL_CONTAINER_ID lua_upvalueindex(4) + +/* + * Only for containers: light udata with item identity. + */ +#define UPVAL_ITEM_ID lua_upvalueindex(5) + +/* + * Only for containers: if not nil, overrides the item count. + */ +#define UPVAL_ITEM_COUNT lua_upvalueindex(6) + +namespace { + /** + * Object references are represented as userdata instances + * with an appropriate metatable; the payload of userdata is + * this structure: + */ + struct DFRefHeader { + void *ptr; + }; + + /* + * The system might be extended to carry some simple + * objects inline inside the reference buffer. + */ + inline bool is_self_contained(DFRefHeader *ptr) { + void **pp = &ptr->ptr; + return **(void****)pp == (pp + 1); + } +} + +/** + * Report an error while accessing a field (index = field name). + */ +static void field_error(lua_State *state, int index, const char *err, const char *mode) +{ + lua_getfield(state, UPVAL_METATABLE, "__metatable"); + const char *cname = lua_tostring(state, -1); + const char *fname = index ? lua_tostring(state, index) : "*"; + luaL_error(state, "Cannot %s field %s.%s: %s.", + mode, (cname ? cname : "?"), (fname ? fname : "?"), err); +} + +/* + * If is_method is true, these use UPVAL_TYPETABLE to save a hash lookup. + */ +static void push_object_internal(lua_State *state, type_identity *type, void *ptr, bool in_method = true); +static void *get_object_internal(lua_State *state, type_identity *type, int val_index, bool in_method = true); + +void DFHack::PushDFObject(lua_State *state, type_identity *type, void *ptr) +{ + push_object_internal(state, type, ptr, false); +} + +void *DFHack::GetDFObject(lua_State *state, type_identity *type, int val_index) +{ + return get_object_internal(state, type, val_index, false); +} + +static void push_adhoc_pointer(lua_State *state, void *ptr, type_identity *target); + +/************************************** + * Identity object read/write methods * + **************************************/ + +void constructed_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + push_object_internal(state, this, ptr); +} + +void constructed_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + field_error(state, fname_idx, "complex object", "write"); +} + +void enum_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + base_type->lua_read(state, fname_idx, ptr); +} + +void enum_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + base_type->lua_write(state, fname_idx, ptr, val_index); +} + +void df::number_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_pushnumber(state, read(ptr)); +} + +void df::number_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + if (!lua_isnumber(state, val_index)) + field_error(state, fname_idx, "number expected", "write"); + + write(ptr, lua_tonumber(state, val_index)); +} + +void df::bool_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_pushboolean(state, *(bool*)ptr); +} + +void df::bool_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + char *pb = (char*)ptr; + + if (lua_isboolean(state, val_index) || lua_isnil(state, val_index)) + *pb = lua_toboolean(state, val_index); + else if (lua_isnumber(state, val_index)) + *pb = lua_tointeger(state, val_index); + else + field_error(state, fname_idx, "boolean or number expected", "write"); +} + +void df::stl_string_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + auto pstr = (std::string*)ptr; + lua_pushlstring(state, pstr->data(), pstr->size()); +} + +void df::stl_string_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + size_t size; + const char *bytes = lua_tolstring(state, val_index, &size); + if (!bytes) + field_error(state, fname_idx, "string expected", "write"); + + *(std::string*)ptr = std::string(bytes, size); +} + +void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr, type_identity *target) +{ + push_object_internal(state, target, *(void**)ptr); +} + +void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_read(state, fname_idx, ptr, target); +} + +void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, + type_identity *target, int val_index) +{ + auto pptr = (void**)ptr; + + if (lua_isnil(state, val_index)) + *pptr = NULL; + else + { + void *nval = get_object_internal(state, target, val_index); + if (nval) + *pptr = nval; + else + field_error(state, fname_idx, "incompatible pointer type", "write"); + } +} + +void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + lua_write(state, fname_idx, ptr, target, val_index); +} + +int container_identity::lua_item_count(lua_State *state, void *ptr) +{ + if (lua_isnumber(state, UPVAL_ITEM_COUNT)) + return lua_tointeger(state, UPVAL_ITEM_COUNT); + else + return item_count(ptr); +} + +void container_identity::lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + push_object_internal(state, id, pitem); +} + +void container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + id->lua_read(state, fname_idx, pitem); +} + +void container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + id->lua_write(state, fname_idx, pitem, val_index); +} + +void ptr_container_identity::lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + push_adhoc_pointer(state, pitem, id); +} + +void ptr_container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(&df::identity_traits::identity, ptr, idx); + df::pointer_identity::lua_read(state, fname_idx, pitem, id); +} + +void ptr_container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(&df::identity_traits::identity, ptr, idx); + df::pointer_identity::lua_write(state, fname_idx, pitem, id, val_index); +} + +void bit_container_identity::lua_item_reference(lua_State *state, int, void *, int) +{ + lua_pushnil(state); +} + +void bit_container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) +{ + lua_pushboolean(state, get_item(ptr, idx)); +} + +void bit_container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +{ + if (lua_isboolean(state, val_index) || lua_isnil(state, val_index)) + set_item(ptr, idx, lua_toboolean(state, val_index)); + else if (lua_isnumber(state, val_index)) + set_item(ptr, idx, lua_tointeger(state, val_index) != 0); + else + field_error(state, fname_idx, "boolean or number expected", "write"); +} + +/* */ + +static int change_error(lua_State *state) +{ + luaL_error(state, "Attempt to change a read-only table.\n"); + return 0; +} + +/** + * Wrap a table so that it can't be modified. + */ +static void freeze_table(lua_State *state, bool leave_metatable = false, const char *name = NULL) +{ + // rv = {}; setmetatable(rv, { __index = in, __newindex = change_error, __metatable = name }) + int base = lua_gettop(state); + lua_newtable(state); + lua_swap(state); + lua_setfield(state, base, "__index"); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_CHANGEERROR_NAME); + lua_setfield(state, base, "__newindex"); + lua_newtable(state); + lua_swap(state); + lua_dup(state); + lua_setmetatable(state, base); + if (name) + { + lua_pushstring(state, name); + lua_setfield(state, -2, "__metatable"); + } + // result: [frozen table] [metatable] + if (!leave_metatable) + lua_pop(state, 1); +} + +static void LookupInTable(lua_State *state, const char *tname) +{ + lua_getfield(state, LUA_REGISTRYINDEX, tname); + lua_swap(state); + lua_rawget(state, -2); + lua_remove(state, -2); +} + +/** + * Look up the key on the stack in DFHACK_TYPETABLE; + * if found, put result on the stack and return true. + */ +static bool LookupTypeInfo(lua_State *state, bool in_method) +{ + // stack: [lookup key] + + if (in_method) + { + lua_rawget(state, UPVAL_TYPETABLE); + } + else + { + LookupInTable(state, DFHACK_TYPETABLE_NAME); + } + + // stack: [info] + + if (lua_isnil(state, -1)) + { + lua_pop(state, 1); + return false; + } + else + return true; +} + +static void LookupInTable(lua_State *state, void *id, const char *tname) +{ + lua_getfield(state, LUA_REGISTRYINDEX, tname); + lua_pushlightuserdata(state, id); + lua_rawget(state, -2); + lua_remove(state, -2); +} + +static void SaveInTable(lua_State *state, void *node, const char *tname) +{ + // stack: [info] + lua_getfield(state, LUA_REGISTRYINDEX, tname); + + lua_pushlightuserdata(state, node); + lua_pushvalue(state, -3); + lua_rawset(state, -3); + + lua_pushvalue(state, -2); + lua_pushlightuserdata(state, node); + lua_rawset(state, -3); + + lua_pop(state, 1); + // stack: [info] +} + +static void SaveTypeInfo(lua_State *state, void *node) +{ + SaveInTable(state, node, DFHACK_TYPETABLE_NAME); +} + +static void BuildTypeMetatable(lua_State *state, type_identity *type); + +/** + * Push the pointer as DF object ref using metatable on the stack. + */ +static void push_object_ref(lua_State *state, void *ptr) +{ + // stack: [metatable] + auto ref = (DFRefHeader*)lua_newuserdata(state, sizeof(DFRefHeader)); + ref->ptr = ptr; + + lua_swap(state); + lua_setmetatable(state, -2); + // stack: [userdata] +} + +static void *get_object_ref(lua_State *state, int val_index) +{ + assert(!lua_islightuserdata(state, val_index)); + + auto ref = (DFRefHeader*)lua_touserdata(state, val_index); + return ref->ptr; +} + +/** + * Push the pointer using given identity. + */ +static void push_object_internal(lua_State *state, type_identity *type, void *ptr, bool in_method) +{ + /* + * If NULL pointer or no type, push something simple + */ + + if (!ptr || !type) + { + if (!ptr) + lua_pushnil(state); + else + lua_pushlightuserdata(state, ptr); + + return; + } + + /* + * Resolve actual class using vtable + */ + + if (type->type() == IDTYPE_CLASS) + { + virtual_identity *class_vid = virtual_identity::get(virtual_ptr(ptr)); + if (class_vid) + type = class_vid; + } + + /* + * Resolve metatable by identity, and push the object + */ + + lua_pushlightuserdata(state, type); // () -> type + + if (!LookupTypeInfo(state, in_method)) // type -> metatable? + BuildTypeMetatable(state, type); // () -> metatable + + push_object_ref(state, ptr); // metatable -> userdata +} + +/** + * Verify that the value matches the identity, and return ptr if so. + */ +static void *get_object_internal(lua_State *state, type_identity *type, int val_index, bool in_method) +{ + /* + * Non-userdata results in NULL; nil for NULL gets handled here too. + */ + if (!lua_isuserdata(state, val_index)) + return NULL; + + /* + * Light user data is allowed with null type; otherwise bail out. + */ + if (!lua_getmetatable(state, val_index)) // () -> metatable? + { + if (!type && lua_islightuserdata(state, val_index)) + return lua_touserdata(state, val_index); + + return NULL; + } + + /* + * Verify that the metatable is known, and refers to the correct type. + * Here doing reverse lookup of identity by metatable. + */ + if (!LookupTypeInfo(state, in_method)) // metatable -> type? + return NULL; + + bool ok = lua_islightuserdata(state, -1) && + (!type || lua_touserdata(state, -1) == type); + + lua_pop(state, 1); // type -> () + + if (!ok) + return NULL; + + /* + * Finally decode the reference. + */ + return get_object_ref(state, val_index); +} + +/** + * Given a DF object reference or type, safely retrieve its identity pointer. + */ +static type_identity *get_object_identity(lua_State *state, int objidx, + const char *ctx, bool allow_type = false) +{ + if (!lua_getmetatable(state, objidx)) + luaL_error(state, "Invalid object in %s", ctx); + + // Verify object type validity + if (lua_isuserdata(state, objidx)) + { + lua_dup(state); + LookupInTable(state, DFHACK_TYPETABLE_NAME); + } + else + { + if (!allow_type) + luaL_error(state, "Object expected in %s", ctx); + + lua_pushvalue(state, objidx); + LookupInTable(state, DFHACK_TYPEID_TABLE_NAME); + } + + if (lua_isnil(state, -1)) + luaL_error(state, "Invalid object metatable in %s", ctx); + lua_pop(state, 1); + + // Extract identity from metatable + lua_getfield(state, -1, "_identity"); + + type_identity *id = (type_identity*)lua_touserdata(state, -1); + if (!id) + luaL_error(state, "Invalid object identity in %s", ctx); + + lua_pop(state, 2); + return id; +} + +/** + * Metamethod: compare two DF object references. + * + * Equal if same pointer and same metatable. + */ +static int meta_ptr_compare(lua_State *state) +{ + if (!lua_isuserdata(state, 1) || !lua_isuserdata(state, 2) || + !lua_getmetatable(state, 1) || !lua_getmetatable(state, 2)) + { + lua_pushboolean(state, false); + return 1; + } + + if (get_object_ref(state, 1) != get_object_ref(state, 2)) + { + lua_pushboolean(state, false); + return 1; + } + + if (!lua_rawequal(state, -1, -2)) + { + // todo: nonidentical type comparison + lua_pushboolean(state, false); + return 1; + } + + lua_pushboolean(state, true); + return 1; +} + +/** + * Method: sizeof for DF object references. + * + * Returns: size[, address] + */ +static int meta_sizeof(lua_State *state) +{ + int argc = lua_gettop(state); + + if (argc != 1) + luaL_error(state, "Usage: object:sizeof() or df.sizeof(object)"); + + // Two special cases: nil and lightuserdata for NULL and void* + if (lua_isnil(state, 1)) + { + lua_pushnil(state); + lua_pushinteger(state, 0); + return 2; + } + + if (lua_islightuserdata(state, 1)) + { + lua_pushnil(state); + lua_pushnumber(state, (size_t)lua_touserdata(state, 1)); + return 2; + } + + type_identity *id = get_object_identity(state, 1, "df.sizeof()", true); + + lua_pushinteger(state, id->byte_size()); + + if (lua_isuserdata(state, 1)) + { + lua_pushnumber(state, (size_t)get_object_ref(state, 1)); + return 2; + } + else + return 1; +} + +/** + * Method: displace for DF object references. + * + * Returns: a reference with the same type, but modified address + */ +static int meta_displace(lua_State *state) +{ + int argc = lua_gettop(state); + + bool has_step = (argc >= 3); + if ((argc < 2 || argc > 3) || + !lua_isnumber(state, 2) || + (has_step && !lua_isnumber(state, 3))) + { + luaL_error(state, "Usage: object:_displace(index[,step]) or df._displace(object,...)"); + } + + int index = lua_tointeger(state, 2); + int step = has_step ? lua_tointeger(state, 3) : 1; + + // Two special cases: nil and lightuserdata for NULL and void* + if (lua_isnil(state, 1)) + { + lua_pushnil(state); + return 1; + } + + if (lua_islightuserdata(state, 1)) + { + if (!has_step) + luaL_error(state, "Step is mandatory in _displace of void*"); + + auto ptr = (uint8_t*)lua_touserdata(state, 1); + lua_pushlightuserdata(state, ptr + index*step); + return 1; + } + + type_identity *id = get_object_identity(state, 1, "df._displace()"); + + if (!has_step) + step = id->byte_size(); + + if (index == 0 || step == 0) + { + lua_pushvalue(state, 1); + } + else + { + auto ptr = (uint8_t*)get_object_ref(state, 1); + lua_getmetatable(state, 1); + push_object_ref(state, ptr + index*step); + } + + return 1; +} + +/** + * Resolve the field name in UPVAL_FIELDTABLE, die if not found. + */ +static void lookup_field(lua_State *state, int index, const char *mode) +{ + lua_pushvalue(state, index); + lua_gettable(state, UPVAL_FIELDTABLE); // uses metatable with enum keys + + if (lua_isnil(state, -1)) + field_error(state, index, "not found", mode); +} + +static void *find_field(lua_State *state, int index, const char *mode) +{ + lookup_field(state, index, mode); + + void *p = lua_touserdata(state, -1); + lua_pop(state, 1); + return p; +} + +/** + * Verify that the object is a DF ref with UPVAL_METATABLE. + * If everything ok, extract the address. + */ +static uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode) +{ + if (!lua_isuserdata(state, obj) || + !lua_getmetatable(state, obj)) + field_error(state, field, "invalid object", mode); + + if (!lua_rawequal(state, -1, UPVAL_METATABLE)) + field_error(state, field, "invalid object metatable", mode); + + lua_pop(state, 1); + + return (uint8_t*)get_object_ref(state, obj); +} + +static void GetAdHocMetatable(lua_State *state, const struct_field_info *field); + +static void read_field(lua_State *state, const struct_field_info *field, void *ptr) +{ + switch (field->mode) + { + case struct_field_info::STATIC_STRING: + { + int len = strnlen((char*)ptr, field->count); + lua_pushlstring(state, (char*)ptr, len); + return; + } + + case struct_field_info::PRIMITIVE: + case struct_field_info::SUBSTRUCT: + field->type->lua_read(state, 2, ptr); + return; + + case struct_field_info::POINTER: + df::pointer_identity::lua_read(state, 2, ptr, field->type); + return; + + case struct_field_info::CONTAINER: + if (!field->eid || !field->type->isContainer() || + field->eid == ((container_identity*)field->type)->getIndexEnumType()) + { + field->type->lua_read(state, 2, ptr); + return; + } + // fallthrough + + case struct_field_info::STATIC_ARRAY: + case struct_field_info::STL_VECTOR_PTR: + GetAdHocMetatable(state, field); + push_object_ref(state, ptr); + return; + + case struct_field_info::END: + break; + } + + lua_pushnil(state); +} + +static void field_reference(lua_State *state, const struct_field_info *field, void *ptr) +{ + switch (field->mode) + { + case struct_field_info::PRIMITIVE: + case struct_field_info::SUBSTRUCT: + push_object_internal(state, field->type, ptr); + return; + + case struct_field_info::POINTER: + push_adhoc_pointer(state, ptr, field->type); + return; + + case struct_field_info::CONTAINER: + read_field(state, field, ptr); + return; + + case struct_field_info::STATIC_STRING: + case struct_field_info::STATIC_ARRAY: + case struct_field_info::STL_VECTOR_PTR: + GetAdHocMetatable(state, field); + push_object_ref(state, ptr); + return; + + case struct_field_info::END: + break; + } + + lua_pushnil(state); +} + +static void write_field(lua_State *state, const struct_field_info *field, void *ptr, int value_idx) +{ + switch (field->mode) + { + case struct_field_info::STATIC_STRING: + { + size_t size; + const char *str = lua_tolstring(state, value_idx, &size); + if (!str) + field_error(state, 2, "string expected", "write"); + memcpy(ptr, str, std::min(size+1, size_t(field->count))); + return; + } + + case struct_field_info::PRIMITIVE: + case struct_field_info::SUBSTRUCT: + case struct_field_info::CONTAINER: + field->type->lua_write(state, 2, ptr, value_idx); + return; + + case struct_field_info::POINTER: + df::pointer_identity::lua_write(state, 2, ptr, field->type, value_idx); + + case struct_field_info::STATIC_ARRAY: + case struct_field_info::STL_VECTOR_PTR: + field_error(state, 2, "complex object", "write"); + + case struct_field_info::END: + return; + } +} + +/** + * Metamethod: represent a type node as string. + */ +static int meta_type_tostring(lua_State *state) +{ + if (!lua_getmetatable(state, 1)) + return 0; + + lua_getfield(state, -1, "__metatable"); + const char *cname = lua_tostring(state, -1); + + lua_pushstring(state, stl_sprintf("", cname).c_str()); + return 1; +} + +/** + * Metamethod: represent a DF object reference as string. + */ +static int meta_ptr_tostring(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 0, "access"); + + lua_getfield(state, UPVAL_METATABLE, "__metatable"); + const char *cname = lua_tostring(state, -1); + + lua_pushstring(state, stl_sprintf("<%s: 0x%08x>", cname, (unsigned)ptr).c_str()); + return 1; +} + +// Resolve the field in the metatable and return +static int get_metafield(lua_State *state) +{ + lua_rawget(state, UPVAL_METATABLE); + return 1; +} + +/** + * Metamethod: __index for structures. + */ +static int meta_struct_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + auto field = (struct_field_info*)find_field(state, 2, "read"); + if (!field) + return get_metafield(state); + read_field(state, field, ptr + field->offset); + return 1; +} + +/** + * Method: _field for structures. + */ +static int meta_struct_field_reference(lua_State *state) +{ + if (lua_gettop(state) != 2) + luaL_error(state, "Usage: object._field(name)"); + uint8_t *ptr = get_object_addr(state, 1, 2, "reference"); + auto field = (struct_field_info*)find_field(state, 2, "reference"); + if (!field) + field_error(state, 2, "builtin property", "reference"); + field_reference(state, field, ptr + field->offset); + return 1; +} + +/** + * Metamethod: __newindex for structures. + */ +static int meta_struct_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + auto field = (struct_field_info*)find_field(state, 2, "write"); + if (!field) + field_error(state, 2, "builtin property", "write"); + write_field(state, field, ptr + field->offset, 3); + return 0; +} + +/** + * Metamethod: __index for primitives, i.e. simple object references. + * Fields point to identity, or NULL for metafields. + */ +static int meta_primitive_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + auto type = (type_identity*)find_field(state, 2, "read"); + if (!type) + return get_metafield(state); + type->lua_read(state, 2, ptr); + return 1; +} + +/** + * Metamethod: __newindex for primitives. + */ +static int meta_primitive_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + auto type = (type_identity*)find_field(state, 2, "write"); + if (!type) + field_error(state, 2, "builtin property", "write"); + type->lua_write(state, 2, ptr, 3); + return 0; +} + +/** + * Metamethod: __len for containers. + */ +static int meta_container_len(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 0, "get length"); + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + lua_pushinteger(state, len); + return 1; +} + +/** + * Field lookup for containers: + * + * - Numbers are indices and handled directly. + * - NULL userdata are metafields; push and exit; + */ +static int lookup_container_field(lua_State *state, int field, const char *mode = NULL) +{ + if (lua_type(state, field) == LUA_TNUMBER) + return field; + + lookup_field(state, field, mode ? mode : "read"); + + if (lua_isuserdata(state, -1) && !lua_touserdata(state, -1)) + { + if (mode) + field_error(state, field, "builtin property", mode); + + lua_pop(state, 1); + get_metafield(state); + return 0; + } + + return -1; +} + +/** + * Index verification: number and in range. + */ +static int check_container_index(lua_State *state, int len, + int fidx, int iidx, const char *mode) +{ + if (!lua_isnumber(state, iidx)) + field_error(state, fidx, "invalid index", mode); + + int idx = lua_tointeger(state, iidx); + if (idx < 0 || idx >= len) + field_error(state, fidx, "index out of bounds", mode); + + return idx; +} + +/** + * Metamethod: __index for containers. + */ +static int meta_container_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + int iidx = lookup_container_field(state, 2); + if (!iidx) + return 1; + + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + int idx = check_container_index(state, len, 2, iidx, "read"); + id->lua_item_read(state, 2, ptr, idx); + return 1; +} + +/** + * Method: _field for containers. + */ +static int meta_container_field_reference(lua_State *state) +{ + if (lua_gettop(state) != 2) + luaL_error(state, "Usage: object._field(index)"); + uint8_t *ptr = get_object_addr(state, 1, 2, "reference"); + int iidx = lookup_container_field(state, 2, "reference"); + + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + int idx = check_container_index(state, len, 2, iidx, "reference"); + id->lua_item_reference(state, 2, ptr, idx); + return 1; +} + +/** + * Metamethod: __index for containers. + */ +static int meta_container_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + int iidx = lookup_container_field(state, 2, "write"); + + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + int idx = check_container_index(state, len, 2, iidx, "write"); + id->lua_item_write(state, 2, ptr, idx, 3); + return 0; +} + +/** + * Metamethod: __len for bitfields. + */ +static int meta_bitfield_len(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 0, "get size"); + auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + lua_pushinteger(state, id->getNumBits()); + return 1; +} + +/** + * Metamethod: __index for bitfields. + */ +static int meta_bitfield_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + int iidx = lookup_container_field(state, 2); + if (!iidx) + return 1; + + auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + // whole + if (lua_isuserdata(state, iidx) && lua_touserdata(state, iidx) == id) + { + size_t intv = 0; + memcpy(&intv, ptr, std::min(sizeof(intv), size_t(id->byte_size()))); + lua_pushnumber(state, intv); + return 1; + } + + int idx = check_container_index(state, id->getNumBits(), 2, iidx, "read"); + int size = id->getBits()[idx].size; + + int value = getBitfieldField(ptr, idx, size); + if (size <= 1) + lua_pushboolean(state, value != 0); + else + lua_pushinteger(state, value); + return 1; +} + +/** + * Metamethod: __newindex for bitfields. + */ +static int meta_bitfield_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + int iidx = lookup_container_field(state, 2, "write"); + + auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + // whole + if (lua_isuserdata(state, iidx) && lua_touserdata(state, iidx) == id) + { + if (!lua_isnumber(state, 3)) + field_error(state, 2, "number expected", "write"); + + size_t intv = (size_t)lua_tonumber(state, 3); + memcpy(ptr, &intv, std::min(sizeof(intv), size_t(id->byte_size()))); + return 0; + } + + int idx = check_container_index(state, id->getNumBits(), 2, iidx, "write"); + int size = id->getBits()[idx].size; + + if (lua_isboolean(state, 3) || lua_isnil(state, 3)) + setBitfieldField(ptr, idx, size, lua_toboolean(state, 3)); + else if (lua_isnumber(state, 3)) + setBitfieldField(ptr, idx, size, lua_tointeger(state, 3)); + else + field_error(state, 2, "number or boolean expected", "write"); + return 0; +} + +/** + * Metamethod: __index for df.global + */ +static int meta_global_index(lua_State *state) +{ + auto field = (struct_field_info*)find_field(state, 2, "read"); + if (!field) + return get_metafield(state); + void *ptr = *(void**)field->offset; + if (!ptr) + field_error(state, 2, "global address not known", "read"); + read_field(state, field, ptr); + return 1; +} + +/** + * Metamethod: __newindex for df.global + */ +static int meta_global_newindex(lua_State *state) +{ + auto field = (struct_field_info*)find_field(state, 2, "write"); + if (!field) + field_error(state, 2, "builtin property", "write"); + void *ptr = *(void**)field->offset; + if (!ptr) + field_error(state, 2, "global address not known", "write"); + write_field(state, field, ptr, 3); + return 0; +} + +/** + * Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack. + */ +static void IndexFields(lua_State *state, struct_identity *pstruct) +{ + // stack: fieldtable + + int base = lua_gettop(state); + + for (struct_identity *p = pstruct; p; p = p->getParent()) + { + auto fields = p->getFields(); + + for (; fields; ++fields) + { + if (fields->mode == struct_field_info::END) + break; + + lua_pushstring(state,fields->name); + lua_pushlightuserdata(state,(void*)fields); + lua_rawset(state,base); + } + } +} + +/** + * Enable a metafield by injecting an entry into a UPVAL_FIELDTABLE. + */ +static void EnableMetaField(lua_State *state, int ftable_idx, const char *name, void *id = NULL) +{ + lua_pushlightuserdata(state, id); + lua_setfield(state, ftable_idx, name); +} + +/** + * Set metatable properties common to all actual DF object references. + */ +static void SetPtrMethods(lua_State *state, int meta_idx, int read_idx) +{ + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_COMPARE_NAME); + lua_setfield(state, meta_idx, "__eq"); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME); + lua_pushvalue(state, meta_idx); + lua_pushcclosure(state, meta_ptr_tostring, 2); + lua_setfield(state, meta_idx, "__tostring"); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); + lua_setfield(state, meta_idx, "sizeof"); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_DISPLACE_NAME); + lua_setfield(state, meta_idx, "_displace"); + + EnableMetaField(state, read_idx, "_type"); + EnableMetaField(state, read_idx, "_kind"); + + EnableMetaField(state, read_idx, "_field"); + + EnableMetaField(state, read_idx, "sizeof"); + EnableMetaField(state, read_idx, "_displace"); +} + +/** + * Add a struct-style (3 upvalues) metamethod to the metatable. + */ +static void SetStructMethod(lua_State *state, int meta_idx, int ftable_idx, + lua_CFunction function, const char *name) +{ + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME); + lua_pushvalue(state, meta_idx); + lua_pushvalue(state, ftable_idx); + lua_pushcclosure(state, function, 3); + lua_setfield(state, meta_idx, name); +} + +/** + * Make a metatable with most common fields, and an empty table for UPVAL_FIELDTABLE. + */ +static void MakeMetatable(lua_State *state, type_identity *type, const char *kind) +{ + int base = lua_gettop(state); + lua_newtable(state); // metatable + + lua_pushstring(state, type->getFullName().c_str()); + lua_setfield(state, base+1, "__metatable"); + + lua_pushlightuserdata(state, type); + lua_setfield(state, base+1, "_identity"); + + LookupInTable(state, type, DFHACK_TYPEID_TABLE_NAME); + if (lua_isnil(state, -1)) + { + // Copy the string from __metatable if no real type + lua_pop(state, 1); + lua_getfield(state, base+1, "__metatable"); + } + lua_setfield(state, base+1, "_type"); + + lua_pushstring(state, kind); + lua_setfield(state, base+1, "_kind"); + + lua_newtable(state); // fieldtable +} + +/** + * Make a struct-style object metatable. + */ +static void MakeFieldMetatable(lua_State *state, struct_identity *pstruct, + lua_CFunction reader, lua_CFunction writer) +{ + int base = lua_gettop(state); + + MakeMetatable(state, pstruct, "struct"); // meta, fields + + IndexFields(state, pstruct); + + SetStructMethod(state, base+1, base+2, reader, "__index"); + SetStructMethod(state, base+1, base+2, writer, "__newindex"); + + // returns: [metatable readfields writefields]; +} + +/** + * Make a primitive-style metatable + */ +static void MakePrimitiveMetatable(lua_State *state, type_identity *type) +{ + int base = lua_gettop(state); + + MakeMetatable(state, type, "primitive"); + + SetPtrMethods(state, base+1, base+2); + + EnableMetaField(state, base+2, "value", type); + + SetStructMethod(state, base+1, base+2, meta_primitive_index, "__index"); + SetStructMethod(state, base+1, base+2, meta_primitive_newindex, "__newindex"); +} + +/** + * Add a 6 upvalue metamethod to the metatable. + */ +static void SetContainerMethod(lua_State *state, int meta_idx, int ftable_idx, + lua_CFunction function, const char *name, + type_identity *container, type_identity *item, int count) +{ + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME); + lua_pushvalue(state, meta_idx); + lua_pushvalue(state, ftable_idx); + + lua_pushlightuserdata(state, container); + lua_pushlightuserdata(state, item); + if (count < 0) + lua_pushnil(state); + else + lua_pushinteger(state, count); + + lua_pushcclosure(state, function, 6); + lua_setfield(state, meta_idx, name); +} + +/** + * If ienum refers to a valid enum, attach its keys to UPVAL_FIELDTABLE, + * and the enum itself to the _enum metafield. + */ +static void AttachEnumKeys(lua_State *state, int base, type_identity *ienum) +{ + LookupInTable(state, ienum, DFHACK_ENUM_TABLE_NAME); + + if (!lua_isnil(state, -1)) + { + lua_newtable(state); + lua_swap(state); + lua_setfield(state, -2, "__index"); + lua_setmetatable(state, base+2); + } + else + lua_pop(state, 1); + + LookupInTable(state, ienum, DFHACK_TYPEID_TABLE_NAME); + lua_setfield(state, base+1, "_enum"); + + EnableMetaField(state, base+2, "_enum"); +} + +/** + * Make a container-style object metatable. + */ +static void MakeContainerMetatable(lua_State *state, container_identity *type, + type_identity *item, int count, type_identity *ienum) +{ + int base = lua_gettop(state); + + MakeMetatable(state, type, "container"); + SetPtrMethods(state, base+1, base+2); + + // Update the type name using full info + lua_pushstring(state, type->getFullName(item).c_str()); + lua_dup(state); + lua_setfield(state, base+1, "__metatable"); + lua_setfield(state, base+1, "_type"); + + lua_pushlightuserdata(state, item); + lua_setfield(state, base+1, "_field_identity"); + + if (count >= 0) + { + lua_pushinteger(state, count); + lua_setfield(state, base+1, "_count"); + } + + SetContainerMethod(state, base+1, base+2, meta_container_len, "__len", type, item, count); + SetContainerMethod(state, base+1, base+2, meta_container_index, "__index", type, item, count); + SetContainerMethod(state, base+1, base+2, meta_container_newindex, "__newindex", type, item, count); + + SetContainerMethod(state, base+1, base+2, meta_container_field_reference, "_field", type, item, count); + + AttachEnumKeys(state, base, ienum); +} + +/* + * Metatable construction identity methods. + */ +void type_identity::build_metatable(lua_State *state) +{ + MakePrimitiveMetatable(state, this); +} + +void container_identity::build_metatable(lua_State *state) +{ + MakeContainerMetatable(state, this, getItemType(), -1, getIndexEnumType()); +} + +void bitfield_identity::build_metatable(lua_State *state) +{ + int base = lua_gettop(state); + + MakeMetatable(state, this, "bitfield"); + + SetPtrMethods(state, base+1, base+2); + + SetContainerMethod(state, base+1, base+2, meta_bitfield_len, "__len", this, NULL, -1); + SetContainerMethod(state, base+1, base+2, meta_bitfield_index, "__index", this, NULL, -1); + SetContainerMethod(state, base+1, base+2, meta_bitfield_newindex, "__newindex", this, NULL, -1); + + AttachEnumKeys(state, base, this); + + EnableMetaField(state, base+2, "whole", this); +} + +void struct_identity::build_metatable(lua_State *state) +{ + int base = lua_gettop(state); + MakeFieldMetatable(state, this, meta_struct_index, meta_struct_newindex); + SetStructMethod(state, base+1, base+2, meta_struct_field_reference, "_field"); + SetPtrMethods(state, base+1, base+2); +} + +void global_identity::build_metatable(lua_State *state) +{ + MakeFieldMetatable(state, this, meta_global_index, meta_global_newindex); +} + +static void BuildTypeMetatable(lua_State *state, type_identity *type) +{ + type->build_metatable(state); + + lua_pop(state, 1); + + SaveTypeInfo(state, type); +} + +/** + * Construct a metatable for an object type folded into the field descriptor. + * This is done to reduce compile-time symbol table bloat due to templates. + */ +static void GetAdHocMetatable(lua_State *state, const struct_field_info *field) +{ + lua_pushlightuserdata(state, (void*)field); + + if (!LookupTypeInfo(state, true)) + { + switch (field->mode) + { + case struct_field_info::CONTAINER: + { + auto ctype = (container_identity*)field->type; + MakeContainerMetatable(state, ctype, ctype->getItemType(), -1, field->eid); + break; + } + + case struct_field_info::STATIC_STRING: + MakeContainerMetatable(state, &df::buffer_container_identity::base_instance, + &df::identity_traits::identity, field->count, NULL); + break; + + case struct_field_info::STATIC_ARRAY: + MakeContainerMetatable(state, &df::buffer_container_identity::base_instance, + field->type, field->count, field->eid); + break; + + case struct_field_info::STL_VECTOR_PTR: + MakeContainerMetatable(state, &df::identity_traits >::identity, + field->type, -1, field->eid); + break; + + default: + luaL_error(state, "Invalid ad-hoc field: %d", field->mode); + } + + lua_pop(state, 1); + + SaveTypeInfo(state, (void*)field); + } +} + +static void push_adhoc_pointer(lua_State *state, void *ptr, type_identity *target) +{ + if (!target) + { + push_object_internal(state, &df::identity_traits::identity, ptr); + return; + } + + LookupInTable(state, target, DFHACK_PTR_IDTABLE_NAME); + + type_identity *id = (type_identity*)lua_touserdata(state, -1); + lua_pop(state, 1); + + if (!id) + { + /* + * HACK: relies on + * 1) pointer_identity destructor being no-op + * 2) lua gc never moving objects in memory + */ + + void *newobj = lua_newuserdata(state, sizeof(pointer_identity)); + id = new (newobj) pointer_identity(target); + + SaveInTable(state, target, DFHACK_PTR_IDTABLE_NAME); + lua_pop(state, 1); + } + + push_object_internal(state, id, ptr); +} + +/* + * Recursive walk of scopes to construct the df... tree. + */ + +static void RenderTypeChildren(lua_State *state, const std::vector &children); + +static void AssociateId(lua_State *state, int table, int val, const char *name) +{ + lua_pushinteger(state, val); + lua_pushstring(state, name); + lua_dup(state); + lua_pushinteger(state, val); + + lua_rawset(state, table); + lua_rawset(state, table); +} + +static void RenderType(lua_State *state, compound_identity *node) +{ + assert(node->getName()); + std::string name = node->getFullName(); + + lua_newtable(state); + if (!lua_checkstack(state, 20)) + return; + + int base = lua_gettop(state); + + switch (node->type()) + { + case IDTYPE_STRUCT: + lua_pushstring(state, "struct-type"); + lua_setfield(state, base, "_kind"); + break; + + case IDTYPE_CLASS: + lua_pushstring(state, "class-type"); + lua_setfield(state, base, "_kind"); + break; + + case IDTYPE_ENUM: + { + lua_pushstring(state, "enum-type"); + lua_setfield(state, base, "_kind"); + + enum_identity *eid = (enum_identity*)node; + const char *const *keys = eid->getKeys(); + + // For enums, set mapping between keys and values + for (int64_t i = eid->getFirstItem(), j = 0; i <= eid->getLastItem(); i++, j++) + { + if (keys[j]) + AssociateId(state, base, i, keys[j]); + } + + if (eid->getFirstItem() <= eid->getLastItem()) + { + lua_pushinteger(state, eid->getFirstItem()); + lua_setfield(state, base, "_first_item"); + + lua_pushinteger(state, eid->getLastItem()); + lua_setfield(state, base, "_last_item"); + } + + SaveInTable(state, node, DFHACK_ENUM_TABLE_NAME); + } + break; + + case IDTYPE_BITFIELD: + { + lua_pushstring(state, "bitfield-type"); + lua_setfield(state, base, "_kind"); + + bitfield_identity *eid = (bitfield_identity*)node; + auto bits = eid->getBits(); + + for (int i = 0; i < eid->getNumBits(); i++) + { + if (bits[i].name) + AssociateId(state, base, i, bits[i].name); + if (bits[i].size > 1) + i += bits[i].size-1; + } + + lua_pushinteger(state, 0); + lua_setfield(state, base, "_first_item"); + + lua_pushinteger(state, eid->getNumBits()-1); + lua_setfield(state, base, "_last_item"); + + SaveInTable(state, node, DFHACK_ENUM_TABLE_NAME); + } + break; + + default: + break; + } + + RenderTypeChildren(state, node->getScopeChildren()); + + assert(base == lua_gettop(state)); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); + lua_setfield(state, base, "sizeof"); + + if (node->type() == IDTYPE_GLOBAL) + { + BuildTypeMetatable(state, node); + + // Set metatable for the inner table + lua_dup(state); + lua_setmetatable(state, base); + lua_swap(state); // -> meta curtable + + freeze_table(state, true, "global"); + + // Copy __newindex to the outer metatable + lua_getfield(state, base, "__newindex"); + lua_setfield(state, -2, "__newindex"); + + lua_remove(state, base); + } + else + { + freeze_table(state, true, name.c_str()); + } + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPE_TOSTRING_NAME); + lua_setfield(state, -2, "__tostring"); + + lua_pushlightuserdata(state, node); + lua_setfield(state, -2, "_identity"); + + lua_pop(state, 1); + + SaveInTable(state, node, DFHACK_TYPEID_TABLE_NAME); +} + +static void RenderTypeChildren(lua_State *state, const std::vector &children) +{ + for (size_t i = 0; i < children.size(); i++) + { + RenderType(state, children[i]); + lua_pushstring(state, children[i]->getName()); + lua_swap(state); + lua_rawset(state, -3); + } +} + +static void DoAttach(lua_State *state) +{ + int base = lua_gettop(state); + + lua_newtable(state); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_PTR_IDTABLE_NAME); + + lua_newtable(state); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_TYPEID_TABLE_NAME); + + lua_newtable(state); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_ENUM_TABLE_NAME); + + lua_pushcfunction(state, change_error); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_CHANGEERROR_NAME); + + lua_pushcfunction(state, meta_ptr_compare); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_COMPARE_NAME); + + lua_pushcfunction(state, meta_type_tostring); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_TYPE_TOSTRING_NAME); + + lua_pushcfunction(state, meta_sizeof); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); + + lua_pushcfunction(state, meta_displace); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_DISPLACE_NAME); + + luaL_register(state, "df", no_functions); + + { + // Assign df a metatable with read-only contents + lua_newtable(state); + + // Render the type structure + RenderTypeChildren(state, compound_identity::getTopScope()); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); + lua_setfield(state, -2, "sizeof"); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_DISPLACE_NAME); + lua_setfield(state, -2, "_displace"); + + freeze_table(state, true, "df"); + lua_remove(state, -2); + lua_setmetatable(state, -2); + } + + lua_pop(state, 1); +} + +/** + * Initialize access to DF objects from the interpreter + * context, unless it has already been done. + */ +void DFHack::AttachDFGlobals(lua_State *state) +{ + if (luaL_newmetatable(state, DFHACK_TYPETABLE_NAME)) + DoAttach(state); + + lua_pop(state, 1); +} diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 99694d949..17751d90d 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -37,6 +37,8 @@ distribution. #undef interface #endif +typedef struct lua_State lua_State; + /* * Definitions of DFHack namespace structs used by generated headers. */ @@ -45,47 +47,252 @@ namespace DFHack { class virtual_class {}; + enum identity_type { + IDTYPE_GLOBAL, + IDTYPE_PRIMITIVE, + IDTYPE_POINTER, + IDTYPE_CONTAINER, + IDTYPE_PTR_CONTAINER, + IDTYPE_BIT_CONTAINER, + IDTYPE_BITFIELD, + IDTYPE_ENUM, + IDTYPE_STRUCT, + IDTYPE_CLASS, + IDTYPE_STL_PTR_VECTOR + }; + + typedef void *(*TAllocateFn)(void*,const void*); + + class DFHACK_EXPORT type_identity { + size_t size; + + protected: + type_identity(size_t size) : size(size) {}; + + void *do_allocate_pod(); + void do_copy_pod(void *tgt, const void *src); + + virtual bool can_allocate() { return true; } + virtual void *do_allocate() { return do_allocate_pod(); } + virtual void do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); } + + public: + virtual ~type_identity() {} + + size_t byte_size() { return size; } + + virtual identity_type type() = 0; + + virtual std::string getFullName() = 0; + + // For internal use in the lua wrapper + virtual void lua_read(lua_State *state, int fname_idx, void *ptr) = 0; + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) = 0; + virtual void build_metatable(lua_State *state); + + virtual bool isContainer() { return false; } + + void *allocate(); + bool copy(void *tgt, const void *src); + }; + + class DFHACK_EXPORT constructed_identity : public type_identity { + TAllocateFn allocator; + + protected: + constructed_identity(size_t size, TAllocateFn alloc) + : type_identity(size), allocator(alloc) {}; + + virtual bool can_allocate() { return (allocator != NULL); } + virtual void *do_allocate() { return allocator(NULL,NULL); } + virtual void do_copy(void *tgt, const void *src) { allocator(tgt,src); } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + class DFHACK_EXPORT compound_identity : public constructed_identity { + static compound_identity *list; + compound_identity *next; + + const char *dfhack_name; + compound_identity *scope_parent; + std::vector scope_children; + static std::vector top_scope; + + protected: + compound_identity(size_t size, TAllocateFn alloc, + compound_identity *scope_parent, const char *dfhack_name); + + virtual void doInit(Core *core); + + public: + const char *getName() { return dfhack_name; } + + virtual std::string getFullName(); + + compound_identity *getScopeParent() { return scope_parent; } + const std::vector &getScopeChildren() { return scope_children; } + static const std::vector &getTopScope() { return top_scope; } + + static void Init(Core *core); + }; + + // Bitfields + struct bitfield_item_info { + const char *name; + int size; + }; + + class DFHACK_EXPORT bitfield_identity : public compound_identity { + const bitfield_item_info *bits; + int num_bits; + + protected: + virtual bool can_allocate() { return true; } + virtual void *do_allocate() { return do_allocate_pod(); } + virtual void do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); } + + public: + bitfield_identity(size_t size, + compound_identity *scope_parent, const char *dfhack_name, + int num_bits, const bitfield_item_info *bits); + + virtual identity_type type() { return IDTYPE_BITFIELD; } + + int getNumBits() { return num_bits; } + const bitfield_item_info *getBits() { return bits; } + + virtual void build_metatable(lua_State *state); + }; + + class DFHACK_EXPORT enum_identity : public compound_identity { + const char *const *keys; + int64_t first_item_value; + int64_t last_item_value; + + type_identity *base_type; + + protected: + virtual bool can_allocate() { return true; } + virtual void *do_allocate(); + virtual void do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); } + + public: + enum_identity(size_t size, + compound_identity *scope_parent, const char *dfhack_name, + type_identity *base_type, + int64_t first_item_value, int64_t last_item_value, + const char *const *keys); + + virtual identity_type type() { return IDTYPE_ENUM; } + + int64_t getFirstItem() { return first_item_value; } + int64_t getLastItem() { return last_item_value; } + int getCount() { return int(last_item_value-first_item_value+1); } + const char *const *getKeys() { return keys; } + + type_identity *getBaseType() { return base_type; } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + struct struct_field_info { + enum Mode { + END, + PRIMITIVE, + STATIC_STRING, + POINTER, + STATIC_ARRAY, + SUBSTRUCT, + CONTAINER, + STL_VECTOR_PTR + }; + Mode mode; + const char *name; + size_t offset; + type_identity *type; + size_t count; + enum_identity *eid; + }; + + class DFHACK_EXPORT struct_identity : public compound_identity { + struct_identity *parent; + std::vector children; + bool has_children; + + const struct_field_info *fields; + + protected: + virtual void doInit(Core *core); + + public: + struct_identity(size_t size, TAllocateFn alloc, + compound_identity *scope_parent, const char *dfhack_name, + struct_identity *parent, const struct_field_info *fields); + + virtual identity_type type() { return IDTYPE_STRUCT; } + + struct_identity *getParent() { return parent; } + const std::vector &getChildren() { return children; } + bool hasChildren() { return has_children; } + + const struct_field_info *getFields() { return fields; } + + bool is_subclass(struct_identity *subtype); + + virtual void build_metatable(lua_State *state); + }; + + class DFHACK_EXPORT global_identity : public struct_identity { + public: + global_identity(const struct_field_info *fields) + : struct_identity(0,NULL,NULL,"global",NULL,fields) {} + + virtual identity_type type() { return IDTYPE_GLOBAL; } + + virtual void build_metatable(lua_State *state); + }; + #ifdef _MSC_VER typedef void *virtual_ptr; #else typedef virtual_class *virtual_ptr; #endif - class DFHACK_EXPORT virtual_identity { - static virtual_identity *list; + class DFHACK_EXPORT virtual_identity : public struct_identity { static std::map known; - - virtual_identity *prev, *next; - const char *dfhack_name; + const char *original_name; - virtual_identity *parent; - std::vector children; - + void *vtable_ptr; - bool has_children; protected: - virtual_identity(const char *dfhack_name, const char *original_name, virtual_identity *parent); + virtual void doInit(Core *core); static void *get_vtable(virtual_ptr instance_ptr) { return *(void**)instance_ptr; } + bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); } + public: - const char *getName() { return dfhack_name; } - const char *getOriginalName() { return original_name ? original_name : dfhack_name; } + virtual_identity(size_t size, TAllocateFn alloc, + const char *dfhack_name, const char *original_name, + virtual_identity *parent, const struct_field_info *fields); - virtual_identity *getParent() { return parent; } - const std::vector &getChildren() { return children; } + virtual identity_type type() { return IDTYPE_CLASS; } + + const char *getOriginalName() { return original_name ? original_name : getName(); } public: static virtual_identity *get(virtual_ptr instance_ptr); - - bool is_subclass(virtual_identity *subtype); + bool is_instance(virtual_ptr instance_ptr) { if (!instance_ptr) return false; if (vtable_ptr) { void *vtable = get_vtable(instance_ptr); if (vtable == vtable_ptr) return true; - if (!has_children) return false; + if (!hasChildren()) return false; } return is_subclass(get(instance_ptr)); } @@ -97,16 +304,11 @@ namespace DFHack } public: - bool can_instantiate() { return (vtable_ptr != NULL); } - virtual_ptr instantiate() { return can_instantiate() ? do_instantiate() : NULL; } + bool can_instantiate() { return can_allocate(); } + virtual_ptr instantiate() { return can_instantiate() ? (virtual_ptr)do_allocate() : NULL; } static virtual_ptr clone(virtual_ptr obj); - protected: - virtual virtual_ptr do_instantiate() = 0; - virtual void do_copy(virtual_ptr tgt, virtual_ptr src) = 0; public: - static void Init(Core *core); - // Strictly for use in virtual class constructors void adjust_vtable(virtual_ptr obj, virtual_identity *main); }; @@ -127,6 +329,23 @@ namespace DFHack void InitDataDefGlobals(Core *core); + // LUA wrapper + + /** + * Make DF objects available to the given interpreter. + */ + DFHACK_EXPORT void AttachDFGlobals(lua_State *state); + + /** + * Push the pointer onto the stack as a wrapped DF object of the given type. + */ + DFHACK_EXPORT void PushDFObject(lua_State *state, type_identity *type, void *ptr); + + /** + * Check that the value is a wrapped DF object of the given type, and if so return the pointer. + */ + DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index); + template T *ifnull(T *a, T *b) { return a ? a : b; } @@ -135,12 +354,6 @@ namespace DFHack size_t size; const T *items; }; - - // Bitfields - struct bitfield_item_info { - const char *name; - int size; - }; } template @@ -164,33 +377,37 @@ inline int linear_index(const DFHack::enum_list_attr &lst, const st namespace df { + using DFHack::type_identity; + using DFHack::compound_identity; using DFHack::virtual_ptr; using DFHack::virtual_identity; using DFHack::virtual_class; + using DFHack::global_identity; + using DFHack::struct_identity; + using DFHack::struct_field_info; using DFHack::bitfield_item_info; + using DFHack::bitfield_identity; + using DFHack::enum_identity; using DFHack::enum_list_attr; using DFHack::BitArray; using DFHack::DfArray; template - struct enum_traits {}; + void *allocator_fn(void *out, const void *in) { + if (out) { *(T*)out = *(const T*)in; return out; } + else return new T(); + } template - struct bitfield_traits {}; + struct identity_traits { + static compound_identity *get() { return &T::_identity; } + }; template - class class_virtual_identity : public virtual_identity { - public: - class_virtual_identity(const char *dfhack_name, const char *original_name, virtual_identity *parent) - : virtual_identity(dfhack_name, original_name, parent) {}; - - T *instantiate() { return static_cast(virtual_identity::instantiate()); } - T *clone(T* obj) { return static_cast(virtual_identity::clone(obj)); } + struct enum_traits {}; - protected: - virtual virtual_ptr do_instantiate() { return new T(); } - virtual void do_copy(virtual_ptr tgt, virtual_ptr src) { *static_cast(tgt) = *static_cast(src); } - }; + template + struct bitfield_traits {}; template struct enum_field { @@ -427,6 +644,24 @@ namespace DFHack { flagarray_to_string(&tmp, val); return join_strings(sep, tmp); } + + // LUA wrapper + + /** + * Push the pointer onto the stack as a wrapped DF object of a specific type. + */ + template + void PushDFObject(lua_State *state, T *ptr) { + PushDFObject(state, df::identity_traits::get(), ptr); + } + + /** + * Check that the value is a wrapped DF object of the correct type, and if so return the pointer. + */ + template + T *GetDFObject(lua_State *state, int val_index) { + return GetDFObject(state, df::identity_traits::get(), val_index); + } } #define ENUM_ATTR(enum,attr,val) (df::enum_traits::attrs(val).attr) diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h new file mode 100644 index 000000000..808634798 --- /dev/null +++ b/library/include/DataIdentity.h @@ -0,0 +1,438 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#pragma once + +#include +#include +#include +#include + +#include "DataDefs.h" + +/* + * Definitions of DFHack namespace structs used by generated headers. + */ + +namespace DFHack +{ + class DFHACK_EXPORT primitive_identity : public type_identity { + public: + primitive_identity(size_t size) : type_identity(size) {}; + + virtual identity_type type() { return IDTYPE_PRIMITIVE; } + }; + + class DFHACK_EXPORT pointer_identity : public primitive_identity { + type_identity *target; + + public: + pointer_identity(type_identity *target = NULL) + : primitive_identity(sizeof(void*)), target(target) {}; + + virtual identity_type type() { return IDTYPE_POINTER; } + + type_identity *getTarget() { return target; } + + std::string getFullName(); + + static void lua_read(lua_State *state, int fname_idx, void *ptr, type_identity *target); + static void lua_write(lua_State *state, int fname_idx, void *ptr, type_identity *target, int val_index); + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + class DFHACK_EXPORT container_identity : public constructed_identity { + type_identity *item; + enum_identity *ienum; + + public: + container_identity(size_t size, TAllocateFn alloc, type_identity *item, enum_identity *ienum = NULL) + : constructed_identity(size, alloc), item(item), ienum(ienum) {}; + + virtual identity_type type() { return IDTYPE_CONTAINER; } + + std::string getFullName() { return getFullName(item); } + + virtual void build_metatable(lua_State *state); + virtual bool isContainer() { return true; } + + type_identity *getItemType() { return item; } + type_identity *getIndexEnumType() { return ienum; } + + virtual std::string getFullName(type_identity *item); + + int lua_item_count(lua_State *state, void *ptr); + + virtual void lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + + protected: + virtual int item_count(void *ptr) = 0; + virtual void *item_pointer(type_identity *item, void *ptr, int idx) = 0; + }; + + class DFHACK_EXPORT ptr_container_identity : public container_identity { + public: + ptr_container_identity(size_t size, TAllocateFn alloc, + type_identity *item, enum_identity *ienum = NULL) + : container_identity(size, alloc, item, ienum) {}; + + virtual identity_type type() { return IDTYPE_PTR_CONTAINER; } + + std::string getFullName(type_identity *item); + + virtual void lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + }; + + class DFHACK_EXPORT bit_container_identity : public container_identity { + public: + bit_container_identity(size_t size, TAllocateFn alloc, enum_identity *ienum = NULL) + : container_identity(size, alloc, NULL, ienum) {}; + + virtual identity_type type() { return IDTYPE_BIT_CONTAINER; } + + std::string getFullName(type_identity *item); + + virtual void lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + + protected: + virtual void *item_pointer(type_identity *, void *, int) { return NULL; } + + virtual bool get_item(void *ptr, int idx) = 0; + virtual void set_item(void *ptr, int idx, bool val) = 0; + }; +} + +namespace df +{ + using DFHack::primitive_identity; + using DFHack::pointer_identity; + using DFHack::container_identity; + using DFHack::ptr_container_identity; + using DFHack::bit_container_identity; + + class number_identity_base : public primitive_identity { + const char *name; + + public: + number_identity_base(size_t size, const char *name) + : primitive_identity(size), name(name) {}; + + std::string getFullName() { return name; } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + + protected: + virtual double read(void *ptr) = 0; + virtual void write(void *ptr, double val) = 0; + }; + + template + class number_identity : public number_identity_base { + public: + number_identity(const char *name) : number_identity_base(sizeof(T), name) {} + protected: + virtual double read(void *ptr) { return double(*(T*)ptr); } + virtual void write(void *ptr, double val) { *(T*)ptr = T(val); } + }; + + class bool_identity : public primitive_identity { + public: + bool_identity() : primitive_identity(sizeof(bool)) {}; + + std::string getFullName() { return "bool"; } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + class stl_string_identity : public primitive_identity { + public: + stl_string_identity() : primitive_identity(sizeof(std::string)) {}; + + std::string getFullName() { return "string"; } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + class stl_ptr_vector_identity : public ptr_container_identity { + public: + /* + * This class assumes that std::vector is equivalent + * in layout and behavior to std::vector for any T. + */ + + stl_ptr_vector_identity(type_identity *item = NULL, enum_identity *ienum = NULL) + : ptr_container_identity(sizeof(std::vector),allocator_fn >,item, ienum) + {}; + + std::string getFullName(type_identity *item) { + return "vector" + ptr_container_identity::getFullName(item); + } + + virtual DFHack::identity_type type() { return DFHack::IDTYPE_STL_PTR_VECTOR; } + + protected: + virtual int item_count(void *ptr) { + return ((std::vector*)ptr)->size(); + }; + virtual void *item_pointer(type_identity *, void *ptr, int idx) { + return &(*(std::vector*)ptr)[idx]; + } + }; + + class buffer_container_identity : public container_identity { + int size; + + public: + buffer_container_identity() + : container_identity(0, NULL, NULL, NULL), size(0) + {} + + buffer_container_identity(int size, type_identity *item, enum_identity *ienum = NULL) + : container_identity(item->byte_size()*size, NULL, item, ienum), size(size) + {} + + std::string getFullName(type_identity *item); + + static buffer_container_identity base_instance; + + protected: + virtual int item_count(void *ptr) { return size; } + virtual void *item_pointer(type_identity *item, void *ptr, int idx) { + return ((uint8_t*)ptr) + idx * item->byte_size(); + } + }; + + template + class stl_container_identity : public container_identity { + const char *name; + + public: + stl_container_identity(const char *name, type_identity *item, enum_identity *ienum = NULL) + : container_identity(sizeof(T), &allocator_fn, item, ienum), name(name) + {} + + std::string getFullName(type_identity *item) { + return name + container_identity::getFullName(item); + } + + protected: + virtual int item_count(void *ptr) { return ((T*)ptr)->size(); } + virtual void *item_pointer(type_identity *item, void *ptr, int idx) { + return &(*(T*)ptr)[idx]; + } + }; + + class bit_array_identity : public bit_container_identity { + public: + /* + * This class assumes that BitArray is equivalent + * in layout and behavior to BitArray for any T. + */ + + typedef BitArray container; + + bit_array_identity(enum_identity *ienum = NULL) + : bit_container_identity(sizeof(container), &allocator_fn, ienum) + {} + + std::string getFullName(type_identity *item) { + return "BitArray<>"; + } + + protected: + virtual int item_count(void *ptr) { return ((container*)ptr)->size * 8; } + virtual bool get_item(void *ptr, int idx) { + return ((container*)ptr)->is_set(idx); + } + virtual void set_item(void *ptr, int idx, bool val) { + ((container*)ptr)->set(idx, val); + } + }; + + class stl_bit_vector_identity : public bit_container_identity { + public: + typedef std::vector container; + + stl_bit_vector_identity(enum_identity *ienum = NULL) + : bit_container_identity(sizeof(container), &allocator_fn, ienum) + {} + + std::string getFullName(type_identity *item) { + return "vector" + bit_container_identity::getFullName(item); + } + + protected: + virtual int item_count(void *ptr) { return ((container*)ptr)->size(); } + virtual bool get_item(void *ptr, int idx) { + return (*(container*)ptr)[idx]; + } + virtual void set_item(void *ptr, int idx, bool val) { + (*(container*)ptr)[idx] = val; + } + }; + +#define NUMBER_IDENTITY_TRAITS(type) \ + template<> struct identity_traits { \ + static number_identity identity; \ + static number_identity_base *get() { return &identity; } \ + }; + + NUMBER_IDENTITY_TRAITS(char); + NUMBER_IDENTITY_TRAITS(int8_t); + NUMBER_IDENTITY_TRAITS(uint8_t); + NUMBER_IDENTITY_TRAITS(int16_t); + NUMBER_IDENTITY_TRAITS(uint16_t); + NUMBER_IDENTITY_TRAITS(int32_t); + NUMBER_IDENTITY_TRAITS(uint32_t); + NUMBER_IDENTITY_TRAITS(int64_t); + NUMBER_IDENTITY_TRAITS(uint64_t); + NUMBER_IDENTITY_TRAITS(float); + + template<> struct identity_traits { + static bool_identity identity; + static bool_identity *get() { return &identity; } + }; + + template<> struct identity_traits { + static stl_string_identity identity; + static stl_string_identity *get() { return &identity; } + }; + + template<> struct identity_traits { + static pointer_identity identity; + static pointer_identity *get() { return &identity; } + }; + + template<> struct identity_traits > { + static stl_ptr_vector_identity identity; + static stl_ptr_vector_identity *get() { return &identity; } + }; + + template<> struct identity_traits > { + static stl_bit_vector_identity identity; + static stl_bit_vector_identity *get() { return &identity; } + }; + +#undef NUMBER_IDENTITY_TRAITS + + // Container declarations + + template struct identity_traits > { + static primitive_identity *get(); + }; + + template struct identity_traits { + static pointer_identity *get(); + }; + + template struct identity_traits { + static container_identity *get(); + }; + + template struct identity_traits > { + static container_identity *get(); + }; + + template struct identity_traits > { + static stl_ptr_vector_identity *get(); + }; + + template struct identity_traits > { + static container_identity *get(); + }; + + template struct identity_traits > { + static bit_container_identity *get(); + }; + + template struct identity_traits > { + static container_identity *get(); + }; + + // Container definitions + + template + primitive_identity *identity_traits >::get() { + return identity_traits::get(); + } + + template + pointer_identity *identity_traits::get() { + static pointer_identity identity(identity_traits::get()); + return &identity; + } + + template + container_identity *identity_traits::get() { + static buffer_container_identity identity(sz, identity_traits::get()); + return &identity; + } + + template + container_identity *identity_traits >::get() { + typedef std::vector container; + static stl_container_identity identity("vector", identity_traits::get()); + return &identity; + } + + template + stl_ptr_vector_identity *identity_traits >::get() { + static stl_ptr_vector_identity identity(identity_traits::get()); + return &identity; + } + + template + container_identity *identity_traits >::get() { + typedef std::deque container; + static stl_container_identity identity("deque", identity_traits::get()); + return &identity; + } + + template + bit_container_identity *identity_traits >::get() { + static type_identity *eid = identity_traits::get(); + static enum_identity *reid = eid->type() == DFHack::IDTYPE_ENUM ? (enum_identity*)eid : NULL; + static bit_array_identity identity(reid); + return &identity; + } + + template + container_identity *identity_traits >::get() { + typedef DfArray container; + static stl_container_identity identity("DfArray", identity_traits::get()); + return &identity; + } +} + diff --git a/library/proto/BasicApi.proto b/library/proto/BasicApi.proto index 7b105ee88..137f25726 100644 --- a/library/proto/BasicApi.proto +++ b/library/proto/BasicApi.proto @@ -4,6 +4,10 @@ option optimize_for = LITE_RUNTIME; import "Basic.proto"; +// RPC GetVersion : EmptyMessage -> StringMessage +// RPC GetDFVersion : EmptyMessage -> StringMessage + +// RPC GetWorldInfo : EmptyMessage -> GetWorldInfoOut message GetWorldInfoOut { enum Mode { MODE_DWARF = 1; @@ -27,6 +31,7 @@ message GetWorldInfoOut { repeated int32 companion_histfig_ids = 10; }; +// RPC ListEnums : EmptyMessage -> ListEnumsOut message ListEnumsOut { repeated EnumItemName material_flags = 1; repeated EnumItemName inorganic_flags = 2; @@ -46,6 +51,7 @@ message ListEnumsOut { repeated EnumItemName profession = 11; }; +// RPC ListMaterials : ListMaterialsIn -> ListMaterialsOut message ListMaterialsIn { optional BasicMaterialInfoMask mask = 1; @@ -62,6 +68,7 @@ message ListMaterialsOut { repeated BasicMaterialInfo value = 1; }; +// RPC ListUnits : ListUnitsIn -> ListUnitsOut message ListUnitsIn { optional BasicUnitInfoMask mask = 1; @@ -81,6 +88,7 @@ message ListUnitsOut { repeated BasicUnitInfo value = 1; }; +// RPC ListSquads : ListSquadsIn -> ListSquadsOut message ListSquadsIn {} message ListSquadsOut { repeated BasicSquadInfo value = 1; diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto index 6f3b84510..92d7c48d9 100644 --- a/library/proto/CoreProtocol.proto +++ b/library/proto/CoreProtocol.proto @@ -62,18 +62,22 @@ message StringListMessage { repeated string value = 1; } +// RPC BindMethod : CoreBindRequest -> CoreBindReply message CoreBindRequest { required string method = 1; required string input_msg = 2; required string output_msg = 3; optional string plugin = 4; } - message CoreBindReply { required int32 assigned_id = 1; } +// RPC RunCommand : CoreRunCommandRequest -> EmptyMessage message CoreRunCommandRequest { required string command = 1; repeated string arguments = 2; } + +// RPC CoreSuspend : EmptyMessage -> IntMessage +// RPC CoreResume : EmptyMessage -> IntMessage diff --git a/library/xml b/library/xml index 08e1f71e8..b28296a8c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 08e1f71e89c1af6b3bef940914ed7f3d8fed89b0 +Subproject commit b28296a8c0c2e5aaa522840598738109018f0146 diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index bf41d7103..ecb5b5023 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -476,28 +476,25 @@ function getSelectedUnit() end end function getxyz() -- this will return pointers x,y and z coordinates. - local off=VersionInfo.getGroup("Position"):getAddress("cursor_xyz") -- lets find where in memory its being held - -- now lets read them (they are double words (or unsigned longs or 4 bits each) and go in sucesion - local x=engine.peekd(off) - local y=engine.peekd(off+4) --next is 4 from start - local z=engine.peekd(off+8) --next is 8 from start - --print("Pointer @:"..x..","..y..","..z) + local x=df.cursor.x + local y=df.cursor.y + local z=df.cursor.z return x,y,z -- return the coords end -function GetCreatureAtPos(x,y,z) -- gets the creature index @ x,y,z coord +function getCreatureAtPos(x,y,z) -- gets the creature index @ x,y,z coord --local x,y,z=getxyz() --get 'X' coords - local vector=engine.peek(VersionInfo.getGroup("Creatures"):getAddress("vector"),ptr_vector) -- load all creatures - for i = 0, vector:size()-1 do -- look into all creatures offsets - local curoff=vector:getval(i) -- get i-th creatures offset - local cx=engine.peek(curoff,ptr_Creature.x) --get its coordinates - local cy=engine.peek(curoff,ptr_Creature.y) - local cz=engine.peek(curoff,ptr_Creature.z) + local vector=df.world.units.all -- load all creatures + for i = 0, vector.size-1 do -- look into all creatures offsets + local curpos=vector[i]:deref().pos --get its coordinates + local cx=curpos.x + local cy=curpos.y + local cz=curpos.z if cx==x and cy==y and cz==z then --compare them - return i --return index + return vector[i]:deref() --return index end end print("Creature not found!") - return -1 + return nil end function Allocate(size) diff --git a/plugins/Dfusion/luafiles/init.lua b/plugins/Dfusion/luafiles/init.lua index ccb51f572..97766a978 100644 --- a/plugins/Dfusion/luafiles/init.lua +++ b/plugins/Dfusion/luafiles/init.lua @@ -72,7 +72,8 @@ table.insert(plugins,{"adv_tools","some tools for (mainly) advneturer hacking"}) table.insert(plugins,{"triggers","a function calling plug (discontinued...)"}) table.insert(plugins,{"migrants","multi race imigrations"}) -table.insert(plugins,{"onfunction","run lua on some df function"})--]=] +--]=] +table.insert(plugins,{"onfunction","run lua on some df function"}) table.insert(plugins,{"editor","edit internals of df",EditDF}) table.insert(plugins,{"saves","run current worlds's init.lua",RunSaved}) loadall(plugins) diff --git a/plugins/Dfusion/luafiles/onfunction/locations.lua b/plugins/Dfusion/luafiles/onfunction/locations.lua index 57043b7a3..7849fc45d 100644 --- a/plugins/Dfusion/luafiles/onfunction/locations.lua +++ b/plugins/Dfusion/luafiles/onfunction/locations.lua @@ -1,5 +1,5 @@ if WINDOWS then --windows function defintions - onfunction.AddFunction(0x55499D+offsets.base(),"Move") --on creature move found with "watch mem=xcoord" + --[=[onfunction.AddFunction(0x55499D+offsets.base(),"Move") --on creature move found with "watch mem=xcoord" onfunction.AddFunction(0x275933+offsets.base(),"Die",{creature="edi"}) --on creature death? found by watching dead flag then stepping until new function onfunction.AddFunction(0x2c1834+offsets.base(),"CreateCreature",{protocreature="eax"}) --arena onfunction.AddFunction(0x349640+offsets.base(),"AddItem",{item="esp"}) --or esp @@ -7,8 +7,9 @@ if WINDOWS then --windows function defintions onfunction.AddFunction(0x3d4301+offsets.base(),"Make_Item",{item_type="esp"}) onfunction.AddFunction(0x5af826+offsets.base(),"Hurt",{target="esi",attacker={off=0x74,rtype=DWORD,reg="esp"}}) onfunction.AddFunction(0x3D5886+offsets.base(),"Flip",{building="esi"}) - onfunction.AddFunction(0x35E340+offsets.base(),"ItemCreate") + onfunction.AddFunction(0x35E340+offsets.base(),"ItemCreate")--]=] + onfunction.AddFunction(4B34B6+offsets.base(),"ReactionFinish") --esp item. Ecx creature, edx? else --linux - onfunction.AddFunction(0x899befe+offsets.base(),"Move") -- found out by attaching watch... - onfunction.AddFunction(0x850eecd+offsets.base(),"Die",{creature="ebx"}) -- same + --[=[onfunction.AddFunction(0x899befe+offsets.base(),"Move") -- found out by attaching watch... + onfunction.AddFunction(0x850eecd+offsets.base(),"Die",{creature="ebx"}) -- same--]=] end diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index a12a01481..43747aba6 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -264,6 +264,11 @@ function tools.empregnate(unit) if unit==nil then unit=getSelectedUnit() end + + if unit==nil then + unit=getCreatureAtPos(getxyz()) + end + if unit==nil then error("Failed to empregnate. Unit not selected/valide") end diff --git a/plugins/proto/rename.proto b/plugins/proto/rename.proto index a6c3b055a..aa1e95f48 100644 --- a/plugins/proto/rename.proto +++ b/plugins/proto/rename.proto @@ -2,6 +2,7 @@ package dfproto; option optimize_for = LITE_RUNTIME; +// RPC RenameSquad : RenameSquadIn -> EmptyMessage message RenameSquadIn { required int32 squad_id = 1; @@ -9,6 +10,7 @@ message RenameSquadIn { optional string alias = 3; } +// RPC RenameUnit : RenameUnitIn -> EmptyMessage message RenameUnitIn { required int32 unit_id = 1;